/**
 * ESLint config rule importer (Pipeline 4).
 * Parses ESLint JSON config files and extracts importable rules.
 * Imports rules with string/regex patterns in config:
 * - no-restricted-imports (paths/patterns)
 * - no-restricted-globals (string array)
 * - no-restricted-properties (object.property pairs)
 * - no-restricted-syntax (AST node type selectors → regex approximations)
 */
import { hashLesson, validateRegex } from './compiler.js';
import { escapeRegex } from './regex-utils.js';
function handleRestrictedImports(ruleName, config, severity, now) {
    const rules = [];
    // Config comes as args array: [{paths: [...], patterns: [...]}]
    const raw = Array.isArray(config) ? config[0] : config;
    if (typeof raw === 'object' && raw !== null) {
        const cfg = raw;
        // Handle paths: ["lodash", "underscore"] or [{name: "lodash"}]
        const paths = Array.isArray(cfg.paths) ? cfg.paths : [];
        for (const p of paths) {
            const name = typeof p === 'string'
                ? p
                : typeof p === 'object' && p !== null && 'name' in p
                    ? String(p.name)
                    : null;
            if (!name)
                continue;
            const heading = `[eslint] ${ruleName}: ${name}`;
            const escapedName = escapeRegex(name);
            rules.push({
                lessonHash: hashLesson(heading, `Import of '${name}' is restricted`),
                lessonHeading: heading,
                pattern: `from\\s+['"]${escapedName}['"/]`,
                message: `Import of '${name}' is restricted by ESLint config.`,
                engine: 'regex',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
            });
        }
        // Handle patterns: ["internal/*"]
        const patterns = Array.isArray(cfg.patterns) ? cfg.patterns : [];
        for (const p of patterns) {
            const pat = typeof p === 'string'
                ? p
                : typeof p === 'object' && p !== null && 'group' in p
                    ? String(p.group)
                    : null;
            if (!pat)
                continue;
            const heading = `[eslint] ${ruleName}: ${pat}`;
            // Convert glob-like pattern to regex
            const regexPat = escapeRegex(pat).replace(/\\\*/g, '.*');
            rules.push({
                lessonHash: hashLesson(heading, `Import matching '${pat}' is restricted`),
                lessonHeading: heading,
                pattern: `from\\s+['"]${regexPat}['"/]`,
                message: `Import matching '${pat}' is restricted by ESLint config.`,
                engine: 'regex',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
            });
        }
    }
    return rules;
}
function handleRestrictedGlobals(_ruleName, config, severity, now) {
    const rules = [];
    if (Array.isArray(config)) {
        for (const item of config) {
            const name = typeof item === 'string'
                ? item
                : typeof item === 'object' && item !== null && 'name' in item
                    ? String(item.name)
                    : null;
            if (!name)
                continue;
            const heading = `[eslint] no-restricted-globals: ${name}`;
            const escapedName = escapeRegex(name);
            rules.push({
                lessonHash: hashLesson(heading, `Use of global '${name}' is restricted`),
                lessonHeading: heading,
                pattern: `\\b${escapedName}\\b`,
                message: `Use of global '${name}' is restricted by ESLint config.`,
                engine: 'regex',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
            });
        }
    }
    return rules;
}
function handleRestrictedProperties(_ruleName, config, severity, now) {
    const rules = [];
    if (!Array.isArray(config))
        return rules;
    for (const item of config) {
        if (typeof item !== 'object' || item === null)
            continue;
        const rec = item;
        const obj = typeof rec.object === 'string' ? rec.object : null;
        const prop = typeof rec.property === 'string' ? rec.property : null;
        if (!obj && !prop)
            continue;
        const label = obj && prop ? `${obj}.${prop}` : (obj ?? `.${prop}`);
        const heading = `[eslint] no-restricted-properties: ${label}`;
        const msg = typeof rec.message === 'string'
            ? rec.message
            : `Use of ${label} is restricted by ESLint config.`;
        // Only use ast-grep for identifiers safe from meta-variable collision
        // ($-prefixed names are ast-grep meta-variables, _ is a wildcard)
        const isAstGrepSafe = (s) => /^[a-zA-Z_$][\w$]*$/.test(s) && !s.startsWith('$') && s !== '_';
        if (obj && prop && isAstGrepSafe(obj) && isAstGrepSafe(prop)) {
            // ast-grep natively matches dot, optional chaining, and bracket notation
            rules.push({
                lessonHash: hashLesson(heading, msg),
                lessonHeading: heading,
                pattern: '',
                astGrepPattern: `${obj}.${prop}`,
                message: msg,
                engine: 'ast-grep',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '!**/*.test.*', '!**/*.spec.*'],
            });
        }
        else if (obj && !prop && isAstGrepSafe(obj)) {
            // ast-grep wildcard matches any property access on the object
            rules.push({
                lessonHash: hashLesson(heading, msg),
                lessonHeading: heading,
                pattern: '',
                astGrepPattern: `${obj}.$PROP`,
                message: msg,
                engine: 'ast-grep',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '!**/*.test.*', '!**/*.spec.*'],
            });
        }
        else {
            // Regex fallback: prop-only, or obj/prop with non-identifier names
            let pattern;
            if (obj && prop) {
                const eo = escapeRegex(obj);
                const ep = escapeRegex(prop);
                pattern = `(?:^|[^\\w$])${eo}\\s*(?:(?:\\.|\\?\\.)\\s*${ep}\\b|\\[\\s*['"]${ep}['"]\\s*\\])`;
            }
            else if (obj) {
                const eo = escapeRegex(obj);
                pattern = `(?:^|[^\\w$])${eo}\\s*(?:\\.|\\?\\.|\\[)`;
            }
            else {
                const ep = escapeRegex(prop);
                pattern = `(?:(?:\\.|\\?\\.)\\s*${ep}\\b|\\[\\s*['"]${ep}['"]\\s*\\])`;
            }
            rules.push({
                lessonHash: hashLesson(heading, msg),
                lessonHeading: heading,
                pattern,
                message: msg,
                engine: 'regex',
                severity,
                compiledAt: now,
                createdAt: now,
                fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '!**/*.test.*', '!**/*.spec.*'],
            });
        }
    }
    return rules;
}
/**
 * Map common ESTree node type selectors to regex approximations.
 * Only includes selectors with high-confidence regex mappings.
 * Complex or ambiguous selectors are skipped during import.
 */
const SYNTAX_REGEX_MAP = {
    ForInStatement: '\\bfor\\s*\\([^;]+?\\bin\\b',
    WithStatement: '\\bwith\\s*\\(',
    DebuggerStatement: '\\bdebugger\\b',
};
function handleRestrictedSyntax(_ruleName, config, severity, now) {
    const rules = [];
    if (!Array.isArray(config))
        return rules;
    for (const item of config) {
        const selector = typeof item === 'string'
            ? item
            : typeof item === 'object' &&
                item !== null &&
                typeof item.selector === 'string'
                ? item.selector
                : null;
        if (!selector)
            continue;
        const pattern = SYNTAX_REGEX_MAP[selector];
        if (!pattern)
            continue; // Skip complex/unknown selectors silently
        const customMsg = typeof item === 'object' && item !== null
            ? item.message
            : undefined;
        const heading = `[eslint] no-restricted-syntax: ${selector}`;
        const message = typeof customMsg === 'string'
            ? customMsg
            : `Use of '${selector}' is restricted by ESLint config.`;
        rules.push({
            lessonHash: hashLesson(heading, message),
            lessonHeading: heading,
            pattern,
            message,
            engine: 'regex',
            severity,
            compiledAt: now,
            createdAt: now,
            fileGlobs: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
        });
    }
    return rules;
}
const IMPORTABLE_HANDLERS = {
    'no-restricted-imports': handleRestrictedImports,
    '@typescript-eslint/no-restricted-imports': handleRestrictedImports,
    'no-restricted-globals': handleRestrictedGlobals,
    'no-restricted-properties': handleRestrictedProperties,
    'no-restricted-syntax': handleRestrictedSyntax,
};
// ─── Parser ─────────────────────────────────────────
/** Parse ESLint JSON config and extract importable rules. */
export function parseEslintConfig(jsonContent) {
    const rules = [];
    const skipped = [];
    let doc;
    try {
        doc = JSON.parse(jsonContent);
    }
    catch {
        return { rules, skipped: [{ rule: '(root)', reason: 'Invalid JSON' }] };
    }
    if (!doc || typeof doc !== 'object') {
        return { rules, skipped: [{ rule: '(root)', reason: 'Config is not an object' }] };
    }
    const config = doc;
    const rulesObj = config.rules;
    if (!rulesObj || typeof rulesObj !== 'object') {
        return { rules, skipped: [{ rule: '(root)', reason: 'No "rules" object found in config' }] };
    }
    const now = new Date().toISOString();
    for (const [ruleName, ruleConfig] of Object.entries(rulesObj)) {
        // ESLint rule config: "off" | "warn" | "error" | 0 | 1 | 2 | [severity, ...options]
        let severity;
        let options;
        if (Array.isArray(ruleConfig)) {
            const sev = ruleConfig[0];
            severity = parseSeverity(sev);
            options = ruleConfig.slice(1);
        }
        else {
            severity = parseSeverity(ruleConfig);
            options = undefined;
        }
        if (severity === 'off')
            continue;
        const handler = IMPORTABLE_HANDLERS[ruleName];
        if (!handler) {
            skipped.push({
                rule: ruleName,
                reason: 'Not an importable rule type (AST-based ESLint rules cannot be converted to regex)',
            });
            continue;
        }
        const imported = handler(ruleName, options, severity, now);
        // Validate each imported rule's regex for syntax and ReDoS safety
        const valid = imported.filter((r) => {
            if (r.engine === 'ast-grep')
                return true; // ast-grep patterns validated separately
            const v = validateRegex(r.pattern);
            if (!v.valid) {
                skipped.push({
                    rule: `${ruleName}: ${r.lessonHeading}`,
                    reason: `Invalid regex: ${v.reason}`,
                });
                return false;
            }
            return true;
        });
        if (valid.length === 0 && imported.length === 0) {
            skipped.push({ rule: ruleName, reason: 'No importable patterns found in rule config' });
        }
        else {
            rules.push(...valid);
        }
    }
    return { rules, skipped };
}
function parseSeverity(sev) {
    if (sev === 'error' || sev === 2)
        return 'error';
    if (sev === 'warn' || sev === 1)
        return 'warning';
    return 'off';
}
//# sourceMappingURL=eslint-adapter.js.map