/**
 * Load project-level normalise overrides from a `.normalise.json` file.
 *
 * Phase 5 of `2026-05-13-NORMALISE-PHASE.md`. Allows users to disable
 * default-on rules or enable opt-in-only rules without editing engine
 * code. The override file lives at the project root (the directory
 * passed to `spv ai analyse <source>`).
 *
 * File shape:
 *
 *   {
 *     "$schema": "https://specverse.dev/normalise.schema.json",
 *     "disabled": ["cluster-module-functions", "drop-trivial-passthrough"],
 *     "enabled":  ["some-opt-in-rule"]
 *   }
 *
 * `disabled` lists rule IDs to skip even though they're enabled by
 * default. `enabled` lists rule IDs to run even though they're opt-in.
 * Both arrays are optional and default to empty.
 *
 * Unknown rule IDs produce a warning (returned via `warnings`) but
 * don't fail the load — engine versions evolve and the user may have
 * a config from a newer version. The runner skips unknown IDs silently.
 */
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { RULES } from './normalise-rules.js';
const CONFIG_FILENAME = '.normalise.json';
/**
 * Load `.normalise.json` from `projectDir`. Returns empty sets when:
 *   - the file doesn't exist (no overrides configured)
 *   - the file is unreadable / not valid JSON / not the expected shape
 *
 * The function NEVER throws — analyse should proceed even when the
 * config is broken. Errors land in `warnings`.
 */
export function loadNormaliseOverrides(projectDir) {
    const empty = {
        disabledRules: new Set(),
        enabledRules: new Set(),
        source: null,
        warnings: [],
    };
    const configPath = join(projectDir, CONFIG_FILENAME);
    if (!existsSync(configPath))
        return empty;
    let raw;
    try {
        raw = readFileSync(configPath, 'utf8');
    }
    catch (err) {
        return {
            ...empty,
            source: configPath,
            warnings: [`Could not read ${CONFIG_FILENAME}: ${err?.message ?? String(err)}`],
        };
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch (err) {
        return {
            ...empty,
            source: configPath,
            warnings: [`${CONFIG_FILENAME} is not valid JSON: ${err?.message ?? String(err)}`],
        };
    }
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        return {
            ...empty,
            source: configPath,
            warnings: [`${CONFIG_FILENAME} must contain a JSON object at the top level`],
        };
    }
    const config = parsed;
    const warnings = [];
    const knownIds = new Set(RULES.map((r) => r.id));
    const disabledRules = new Set();
    const enabledRules = new Set();
    if ('disabled' in config) {
        const disabled = config.disabled;
        if (!Array.isArray(disabled)) {
            warnings.push(`${CONFIG_FILENAME}: "disabled" must be an array of rule IDs (got ${typeof disabled})`);
        }
        else {
            for (const id of disabled) {
                if (typeof id !== 'string') {
                    warnings.push(`${CONFIG_FILENAME}: entry in "disabled" is not a string (got ${typeof id})`);
                    continue;
                }
                if (!knownIds.has(id)) {
                    warnings.push(`${CONFIG_FILENAME}: unknown rule ID "${id}" in "disabled" (known: ${[...knownIds].join(', ')})`);
                }
                // We still add unknown IDs to the set — they're harmless (no
                // rule with that ID will match) and the warning is enough.
                disabledRules.add(id);
            }
        }
    }
    if ('enabled' in config) {
        const enabled = config.enabled;
        if (!Array.isArray(enabled)) {
            warnings.push(`${CONFIG_FILENAME}: "enabled" must be an array of rule IDs (got ${typeof enabled})`);
        }
        else {
            for (const id of enabled) {
                if (typeof id !== 'string') {
                    warnings.push(`${CONFIG_FILENAME}: entry in "enabled" is not a string (got ${typeof id})`);
                    continue;
                }
                if (!knownIds.has(id)) {
                    warnings.push(`${CONFIG_FILENAME}: unknown rule ID "${id}" in "enabled"`);
                }
                enabledRules.add(id);
            }
        }
    }
    return { disabledRules, enabledRules, source: configPath, warnings };
}
//# sourceMappingURL=load-overrides.js.map