import { messages } from '../../../core/messages/index.js';
import { isLikelyIntentionalDuplicate, lintCssLikeStylelint } from '../../../shared/formatters/index.js';
import postcss, {} from 'postcss';
import postcssSass from 'postcss-sass';
import postcssScss from 'postcss-scss';
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
const disableEnv = globalThis.process?.env?.TUTANUS_DISABLE_PLUGIN_CSS === '1';
function warn(message, relPath, line, nivelArg = messages.SeverityNiveis.warning) {
    return criarOcorrencia({
        relPath,
        mensagem: message,
        linha: line,
        nivel: nivelArg,
        origem: messages.AnalystOrigens.css,
        tipo: messages.AnalystTipos.css
    });
}
function collectCssIssues(src, relPath) {
    const ocorrencias = [];
    const lines = src.split(/\n/);
    const stack = [];
    lines.forEach((lineText, idx) => {
        const line = idx + 1;
        const trimmed = lineText.trim();
        const opens = (trimmed.match(/\{/g) || []).length;
        for (let indice = 0; indice < opens; indice++) {
            const ctx = /@font-face/i.test(trimmed) ? 'font-face' : undefined;
            stack.push({
                props: {},
                context: ctx
            });
        }
        const current = stack[stack.length - 1];
        if (current) {
            const propMatch = /^([a-zA-Z-]+)\s*:\s*([^;]+)?/.exec(trimmed);
            if (propMatch) {
                const prop = propMatch[1].toLowerCase();
                const value = (propMatch[2] || '').trim();
                const prev = current.props[prop];
                if (prev) {
                    const ctx = current.context ? {
                        currentAtRule: current.context
                    } : undefined;
                    if (isLikelyIntentionalDuplicate(prop, prev.value, value, ctx)) {
                    }
                    else if (prev.value === value) {
                        ocorrencias.push(warn(messages.CssMensagens.duplicatePropertySame(prop), relPath, line));
                    }
                    else {
                        ocorrencias.push(warn(messages.CssMensagens.duplicatePropertyDifferent(prop, prev.value, value), relPath, line));
                    }
                }
                else {
                    current.props[prop] = {
                        line,
                        value
                    };
                }
            }
        }
        if (/!important/.test(trimmed)) {
            ocorrencias.push(warn(messages.CssMensagens.importantUsage, relPath, line));
        }
        if (/^@import\s+[^;]*http:\/\//i.test(trimmed)) {
            ocorrencias.push(warn(messages.CssMensagens.httpImport, relPath, line));
        }
        if (/url\(\s*['"]?http:\/\//i.test(trimmed)) {
            ocorrencias.push(warn(messages.CssMensagens.httpUrl, relPath, line));
        }
        const closes = (trimmed.match(/\}/g) || []).length;
        for (let indice = 0; indice < closes; indice++) {
            if (stack.length)
                stack?.pop();
        }
    });
    return ocorrencias;
}
function collectCssIssuesLikeStylelint(src, relPath) {
    const warnings = lintCssLikeStylelint({
        code: src,
        relPath
    });
    if (!warnings.length)
        return null;
    return warnings.map(dimensaoLargura => {
        const nivel = dimensaoLargura.severity === 'error' ? messages.SeverityNiveis.error : messages.SeverityNiveis.warning;
        return warn(dimensaoLargura.text, relPath, dimensaoLargura.line, nivel);
    });
}
function normalizeCssValue(value) {
    return value.replace(/\s+/g, ' ').trim();
}
function canonicalizeDecls(decls) {
    const map = new Map();
    decls.forEach(diretorio => {
        const prop = diretorio.prop.trim().toLowerCase();
        const value = normalizeCssValue(diretorio.value);
        if (!prop || !value)
            return;
        map.set(prop, value);
    });
    const sorted = [...map.entries()].sort((arquivo, base) => arquivo[0].localeCompare(base[0]));
    return {
        signature: sorted.map(([caminho, valor]) => `${caminho}:${valor}`).join(';'),
        count: sorted.length
    };
}
function splitSimpleSelectors(selectorText) {
    return selectorText.split(',').map(string => string.trim()).filter(Boolean).filter(string => /^(\.[A-Za-z0-9_-]+|#[A-Za-z0-9_-]+)$/.test(string));
}
function parseWithPostCssRoots(src, relPath) {
    const isScss = /\.scss$/i.test(relPath);
    const isSass = /\.sass$/i.test(relPath);
    const syntax = isScss ? postcssScss : isSass ? postcssSass : undefined;
    try {
        const result = postcss().process(src, {
            from: relPath,
            ...(syntax ? {
                syntax
            } : {})
        });
        const rootUnknown = result.root;
        const rootAny = rootUnknown;
        if (rootAny && rootAny.type === 'document' && Array.isArray(rootAny.nodes)) {
            const roots = rootAny.nodes.filter((node) => {
                const textoTitulo = node?.type;
                return textoTitulo === 'root';
            });
            return roots.length ? roots : null;
        }
        return [result.root];
    }
    catch {
        return null;
    }
}
function getNodeLine(node) {
    const line = node?.source?.start?.line;
    return typeof line === 'number' && Number.isFinite(line) ? line : undefined;
}
function formatAtRuleContext(node) {
    const name = String(node.name || '').toLowerCase();
    const params = String(node.params || '').trim();
    return `@${name}${params ? ` ${params}` : ''}`;
}
function normalizePropKey(prop) {
    return prop.startsWith('--') ? prop : prop.toLowerCase();
}
const CONHECIDAS_CSS_PROPRIEDADES = new Set([
    'display', 'position', 'top', 'right', 'bottom', 'left', 'width', 'height', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-width', 'border-style', 'border-color', 'box-sizing', 'float', 'clear', 'overflow', 'overflow-x', 'overflow-y', 'z-index', 'visibility',
    'flex', 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content', 'align-items', 'align-content', 'align-self', 'flex-grow', 'flex-shrink', 'flex-basis', 'order',
    'grid', 'grid-template-columns', 'grid-template-rows', 'grid-template-areas', 'grid-column', 'grid-row', 'grid-area', 'gap', 'row-gap', 'column-gap',
    'font', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant', 'line-height', 'letter-spacing', 'word-spacing', 'text-align', 'text-decoration', 'text-transform', 'text-indent', 'white-space', 'word-break', 'word-wrap', 'color', 'background', 'background-color', 'background-image', 'background-repeat', 'background-position', 'background-size',
    'transform-origin', 'inset', 'background-clip', 'max-width', 'max-height', 'min-height', 'animation-delay', 'pointer-events', 'scroll-behavior', 'background-attachment', 'text-shadow', 'inset-inline', 'inset-block', 'inset-inline-start', 'inset-inline-end', 'inset-block-start', 'inset-block-end',
    'cursor', 'opacity', 'transform', 'transition', 'animation', 'list-style', 'vertical-align', 'text-overflow', 'box-shadow', 'border-radius', 'outline', 'content', 'counter-reset', 'counter-increment'
]);
function isValidCssProperty(prop) {
    const normalized = prop.toLowerCase();
    if (normalized.startsWith('--') || normalized.startsWith('-')) {
        return true;
    }
    return CONHECIDAS_CSS_PROPRIEDADES.has(normalized);
}
function detectCssHacks(value) {
    const hacks = [{
            pattern: /\\9/,
            name: 'backslash-9 hack'
        }, {
            pattern: /_property/,
            name: 'underscore hack'
        }, {
            pattern: /\*property/,
            name: 'star hack'
        }, {
            pattern: /property\\\0\//,
            name: 'null byte hack'
        }, {
            pattern: /expression\(/,
            name: 'IE expression'
        }];
    for (const hack of hacks) {
        if (hack.pattern.test(value)) {
            return hack.name;
        }
    }
    return null;
}
function collectCssIssuesFromPostCssAst(root, relPath) {
    const ocorrencias = [];
    const seen = new Set();
    const pushOnce = (metrica) => {
        const chave = `${metrica.mensagem}|${metrica.relPath}|${metrica.linha ?? 0}`;
        if (seen.has(chave))
            return;
        seen.add(chave);
        ocorrencias.push(metrica);
    };
    const byContextAndSignature = new Map();
    const visit = (container, ctxAtRules, ctxSelectors) => {
        const props = {};
        const inFontFace = ctxAtRules.some(elem => elem.toLowerCase().startsWith('@font-face'));
        container.nodes?.forEach(node => {
            if (node.type === 'decl') {
                const decl = node;
                const propRaw = String(decl.prop || '').trim();
                if (!propRaw || propRaw.startsWith('$')) {
                    return;
                }
                const propChave = normalizePropKey(propRaw);
                const value = normalizeCssValue(`${String(decl.value || '')}${decl.important ? ' !important' : ''}`);
                const line = getNodeLine(decl);
                const prev = props[propChave];
                if (prev) {
                    const ctx = inFontFace ? {
                        currentAtRule: 'font-face'
                    } : undefined;
                    if (isLikelyIntentionalDuplicate(propChave, prev.value, value, ctx)) {
                    }
                    else if (prev.value === value) {
                        pushOnce(warn(messages.CssMensagens.duplicatePropertySame(propRaw), relPath, line));
                    }
                    else {
                        pushOnce(warn(messages.CssMensagens.duplicatePropertyDifferent(propRaw, prev.value, value), relPath, line));
                    }
                }
                props[propChave] = {
                    value,
                    line
                };
                if (!isValidCssProperty(propRaw)) {
                    pushOnce(warn(messages.CssMensagens.invalidProperty(propRaw), relPath, line));
                }
                const hack = detectCssHacks(String(decl.value || ''));
                if (hack) {
                    pushOnce(warn(messages.CssMensagens.cssHackDetected(hack), relPath, line));
                }
                if (decl.important) {
                    pushOnce(warn(messages.CssMensagens.importantUsage, relPath, line));
                }
                if (/url\(\s*['"]?http:\/\//i.test(String(decl.value || ''))) {
                    pushOnce(warn(messages.CssMensagens.httpUrl, relPath, line));
                }
            }
        });
        if (container.type === 'rule') {
            const rule = container;
            const selectorText = String(rule.selector || '');
            const simpleSelectors = splitSimpleSelectors(selectorText);
            if (simpleSelectors.length) {
                const decls = [];
                rule.nodes?.forEach(node => {
                    if (node.type !== 'decl')
                        return;
                    const diretorio = node;
                    const prop = String(diretorio.prop || '').trim();
                    if (!prop || prop.startsWith('$'))
                        return;
                    const value = `${String(diretorio.value || '')}${diretorio.important ? ' !important' : ''}`;
                    decls.push({
                        prop,
                        value
                    });
                });
                const hasApplyDirective = rule.nodes?.some(node => node.type === 'atrule' && node.name === 'apply') ?? false;
                if (decls.length === 0 && !hasApplyDirective) {
                    pushOnce(warn(messages.CssMensagens.emptyRule, relPath, getNodeLine(rule)));
                }
                const { signature, count } = canonicalizeDecls(decls);
                if (signature && count >= 3) {
                    const ctxChave = [...ctxAtRules, ...ctxSelectors].join('|');
                    const key = `${ctxChave}||${signature}`;
                    const entry = byContextAndSignature.get(key) ?? {
                        declCount: count,
                        hits: []
                    };
                    const line = getNodeLine(rule);
                    simpleSelectors.forEach(sel => entry.hits.push({
                        selector: sel,
                        line
                    }));
                    byContextAndSignature.set(key, entry);
                }
            }
            else if (selectorText.trim()) {
                try {
                    const invalidChars = /[{}[\]()]/;
                    if (invalidChars.test(selectorText)) {
                        pushOnce(warn(messages.CssMensagens.malformedSelector(selectorText.trim()), relPath, getNodeLine(rule)));
                    }
                }
                catch {
                    pushOnce(warn(messages.CssMensagens.malformedSelector(selectorText.trim()), relPath, getNodeLine(rule)));
                }
            }
        }
        container.nodes?.forEach(node => {
            if (node.type === 'atrule') {
                const at = node;
                const name = String(at.name || '').toLowerCase();
                const line = getNodeLine(at);
                if (name === 'import' && /^\s*(url\()?\s*['"]?http:\/\//i.test(String(at.params || ''))) {
                    pushOnce(warn(messages.CssMensagens.httpImport, relPath, line));
                }
                if (at.nodes && at.nodes.length) {
                    visit(at, [...ctxAtRules, formatAtRuleContext(at)], ctxSelectors);
                }
                return;
            }
            if (node.type === 'rule') {
                const raiz = node;
                const nextSelectors = container.type === 'rule' ? [...ctxSelectors, `sel ${String(container.selector || '').trim()}`] : ctxSelectors;
                visit(raiz, ctxAtRules, nextSelectors);
            }
        });
    };
    visit(root, [], []);
    for (const { declCount, hits } of byContextAndSignature.values()) {
        const selectors = [...new Set(hits.map(hash => hash.selector))];
        if (selectors.length < 2)
            continue;
        const hasSemanticNames = selectors.every(sel => /^[.#][A-Z]/.test(sel) || /[A-Z]/.test(sel.replace(/^[.#]/, '')));
        if (hasSemanticNames && selectors.length === 2)
            continue;
        const line = hits.map(hash => hash.line).filter((node) => typeof node === 'number' && Number.isFinite(node)).sort((arquivo, base) => arquivo - base)[0];
        pushOnce(warn(messages.CssMensagens.unifySelectors(selectors, declCount), relPath, line));
        const ids = selectors.filter(string => string.startsWith('#'));
        ids.slice(0, 3).forEach(idSel => {
            pushOnce(warn(messages.CssMensagens.idSelectorPreferClass(idSel), relPath, line, messages.SeverityNiveis.info));
        });
    }
    return ocorrencias;
}
export const analistaCss = criarAnalista({
    nome: 'analista-css',
    categoria: 'estilo',
    descricao: 'Lint de CSS do Tutanus (com fallback heurístico).',
    global: false,
    test: (relPath) => /\.(css|scss|sass)$/i.test(relPath),
    aplicar: async (src, relPath) => {
        if (disableEnv)
            return null;
        const isCss = /\.css$/i.test(relPath);
        const roots = parseWithPostCssRoots(src, relPath);
        const astIssues = roots ? roots.flatMap(raiz => collectCssIssuesFromPostCssAst(raiz, relPath)) : [];
        const cssLint = isCss ? collectCssIssuesLikeStylelint(src, relPath) ?? [] : [];
        const seen = new Set();
        const merged = [];
        const pushOnce = (metrica) => {
            const chave = `${metrica.mensagem}|${metrica.relPath}|${metrica.linha ?? 0}`;
            if (seen.has(chave))
                return;
            seen.add(chave);
            merged.push(metrica);
        };
        cssLint.forEach(pushOnce);
        astIssues.forEach(pushOnce);
        if (merged.length)
            return merged;
        const fallback = collectCssIssues(src, relPath);
        return fallback.length ? fallback : null;
    }
});
//# sourceMappingURL=analyst-css.js.map