import { createRule } from '../utils/index.js';
import { findAttribute, getLangValue } from '../utils/ast-utils.js';
export default createRule('block-lang', {
    meta: {
        docs: {
            description: 'disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.',
            category: 'Best Practices',
            recommended: false
        },
        schema: [
            {
                type: 'object',
                properties: {
                    enforceScriptPresent: {
                        type: 'boolean'
                    },
                    enforceStylePresent: {
                        type: 'boolean'
                    },
                    script: {
                        oneOf: [
                            {
                                type: ['string', 'null']
                            },
                            {
                                type: 'array',
                                items: {
                                    type: ['string', 'null']
                                },
                                minItems: 1
                            }
                        ]
                    },
                    style: {
                        oneOf: [
                            {
                                type: ['string', 'null']
                            },
                            {
                                type: 'array',
                                items: {
                                    type: ['string', 'null']
                                },
                                minItems: 1
                            }
                        ]
                    }
                },
                additionalProperties: false
            }
        ],
        messages: {},
        type: 'suggestion',
        hasSuggestions: true
    },
    create(context) {
        if (!context.sourceCode.parserServices.isSvelte) {
            return {};
        }
        const enforceScriptPresent = context.options[0]?.enforceScriptPresent ?? false;
        const enforceStylePresent = context.options[0]?.enforceStylePresent ?? false;
        const scriptOption = context.options[0]?.script ?? null;
        const allowedScriptLangs = Array.isArray(scriptOption)
            ? scriptOption
            : [scriptOption];
        const scriptNodes = [];
        const styleOption = context.options[0]?.style ?? null;
        const allowedStyleLangs = Array.isArray(styleOption)
            ? styleOption
            : [styleOption];
        const styleNodes = [];
        return {
            SvelteScriptElement(node) {
                scriptNodes.push(node);
            },
            SvelteStyleElement(node) {
                styleNodes.push(node);
            },
            'Program:exit'() {
                if (scriptNodes.length === 0 && enforceScriptPresent) {
                    context.report({
                        loc: { line: 1, column: 1 },
                        message: `The <script> block should be present and its lang attribute should be ${prettyPrintLangs(allowedScriptLangs)}.`,
                        suggest: buildAddLangSuggestions(allowedScriptLangs, 'script', context.sourceCode)
                    });
                }
                for (const scriptNode of scriptNodes) {
                    if (!allowedScriptLangs.includes(getLangValue(scriptNode)?.toLowerCase() ?? null)) {
                        context.report({
                            node: scriptNode,
                            message: `The lang attribute of the <script> block should be ${prettyPrintLangs(allowedScriptLangs)}.`,
                            suggest: buildReplaceLangSuggestions(allowedScriptLangs, scriptNode)
                        });
                    }
                }
                if (styleNodes.length === 0 && enforceStylePresent) {
                    const sourceCode = context.sourceCode;
                    context.report({
                        loc: { line: 1, column: 1 },
                        message: `The <style> block should be present and its lang attribute should be ${prettyPrintLangs(allowedStyleLangs)}.`,
                        suggest: buildAddLangSuggestions(allowedStyleLangs, 'style', sourceCode)
                    });
                }
                for (const styleNode of styleNodes) {
                    if (!allowedStyleLangs.includes(getLangValue(styleNode)?.toLowerCase() ?? null)) {
                        context.report({
                            node: styleNode,
                            message: `The lang attribute of the <style> block should be ${prettyPrintLangs(allowedStyleLangs)}.`,
                            suggest: buildReplaceLangSuggestions(allowedStyleLangs, styleNode)
                        });
                    }
                }
            }
        };
    }
});
function buildAddLangSuggestions(langs, tagName, sourceCode) {
    return langs
        .filter((lang) => lang != null && lang !== '')
        .map((lang) => {
        return {
            desc: `Add a lang attribute to a <${tagName}> block with the value "${lang}".`,
            fix: (fixer) => {
                const langAttributeText = getLangAttributeText(lang ?? '', true);
                return fixer.insertTextAfterRange(tagName === 'script' ? [0, 0] : [sourceCode.text.length, sourceCode.text.length], `<${tagName}${langAttributeText}>\n</${tagName}>\n\n`);
            }
        };
    });
}
function buildReplaceLangSuggestions(langs, node) {
    const tagName = node.name.name;
    const langAttribute = findAttribute(node, 'lang');
    const filteredLangs = langs.filter((lang) => lang != null && lang !== '');
    if (filteredLangs.length === 0 && langs.includes(null) && langAttribute !== null) {
        return [
            {
                desc: `Replace a <${tagName}> block with the lang attribute omitted.`,
                fix: (fixer) => {
                    return fixer.remove({
                        type: langAttribute.type,
                        range: [langAttribute.range[0] - 1, langAttribute.range[1]]
                    });
                }
            }
        ];
    }
    return filteredLangs.map((lang) => {
        const langAttributeText = getLangAttributeText(lang ?? '', true);
        if (langAttribute) {
            return {
                desc: `Replace a <${tagName}> block with the lang attribute set to "${lang}".`,
                fix: (fixer) => {
                    return fixer.replaceText(langAttribute, langAttributeText.trim());
                }
            };
        }
        return {
            desc: `Add lang attribute to a <${tagName}> block with the value "${lang}".`,
            fix: (fixer) => {
                return fixer.insertTextBeforeRange([node.startTag.range[0] + tagName.length + 1, 0], langAttributeText);
            }
        };
    });
}
/**
 * Prints the list of allowed languages, with special handling of the `null` option.
 */
function prettyPrintLangs(langs) {
    const hasNull = langs.includes(null);
    const nonNullLangs = langs.filter((lang) => lang !== null).map((lang) => `"${lang}"`);
    if (nonNullLangs.length === 0) {
        // No special behavior for `hasNull`, because that can never happen.
        return 'omitted';
    }
    const hasNullText = hasNull ? 'either omitted or ' : '';
    const nonNullText = nonNullLangs.length === 1 ? nonNullLangs[0] : `one of ${nonNullLangs.join(', ')}`;
    return hasNullText + nonNullText;
}
/**
 * Returns the lang attribute text, with special handling of the `null` lang option with respect to the `prependWhitespace` argument.
 */
function getLangAttributeText(lang, prependWhitespace) {
    return `${prependWhitespace ? ' ' : ''}lang="${lang}"`;
}
