"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadUnifiedConfig = loadUnifiedConfig;
const fs_1 = require("fs");
const path = __importStar(require("path"));
const toml_1 = require("@iarna/toml");
const hash_1 = require("./hash");
const RuleProcessor_1 = require("./RuleProcessor");
const FileSystemUtils = __importStar(require("./FileSystemUtils"));
async function loadUnifiedConfig(options) {
    // Resolve the effective .ruler directory (local or global), mirroring the main loader behavior
    const resolvedRulerDir = (await FileSystemUtils.findRulerDir(options.projectRoot, true)) ||
        path.join(options.projectRoot, '.ruler');
    const meta = {
        projectRoot: options.projectRoot,
        rulerDir: resolvedRulerDir,
        loadedAt: new Date(),
        version: '0.0.0-dev',
    };
    const diagnostics = [];
    // Read TOML if available
    let tomlRaw = {};
    const tomlFile = options.configPath
        ? path.resolve(options.configPath)
        : path.join(meta.rulerDir, 'ruler.toml');
    try {
        const text = await fs_1.promises.readFile(tomlFile, 'utf8');
        tomlRaw = text.trim() ? (0, toml_1.parse)(text) : {};
        meta.configFile = tomlFile;
    }
    catch (err) {
        if (err.code !== 'ENOENT') {
            diagnostics.push({
                severity: 'warning',
                code: 'TOML_READ_ERROR',
                message: 'Failed to read ruler.toml',
                file: tomlFile,
                detail: err.message,
            });
        }
    }
    let defaultAgents;
    if (tomlRaw &&
        typeof tomlRaw === 'object' &&
        tomlRaw.default_agents &&
        Array.isArray(tomlRaw.default_agents)) {
        defaultAgents = tomlRaw.default_agents.map((a) => String(a));
    }
    let nested = false;
    if (tomlRaw &&
        typeof tomlRaw === 'object' &&
        typeof tomlRaw.nested === 'boolean') {
        nested = tomlRaw.nested;
    }
    // Parse skills configuration
    let skillsConfig;
    if (tomlRaw && typeof tomlRaw === 'object') {
        const skillsSection = tomlRaw.skills;
        if (skillsSection && typeof skillsSection === 'object') {
            const skillsObj = skillsSection;
            if (typeof skillsObj.enabled === 'boolean') {
                skillsConfig = { enabled: skillsObj.enabled };
            }
        }
    }
    const toml = {
        raw: tomlRaw,
        schemaVersion: 1,
        agents: {},
        defaultAgents,
        nested,
        skills: skillsConfig,
    };
    // Collect rule markdown files
    let ruleFiles = [];
    try {
        const dirEntries = await fs_1.promises.readdir(meta.rulerDir, { withFileTypes: true });
        const mdFiles = dirEntries
            .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
            .map((e) => path.join(meta.rulerDir, e.name));
        // Sort lexicographically then ensure AGENTS.md first
        mdFiles.sort((a, b) => a.localeCompare(b));
        mdFiles.sort((a, b) => {
            const aIs = /agents\.md$/i.test(a);
            const bIs = /agents\.md$/i.test(b);
            if (aIs && !bIs)
                return -1;
            if (bIs && !aIs)
                return 1;
            return 0;
        });
        let order = 0;
        ruleFiles = await Promise.all(mdFiles.map(async (file) => {
            const content = await fs_1.promises.readFile(file, 'utf8');
            const stat = await fs_1.promises.stat(file);
            return {
                path: file,
                relativePath: path.basename(file),
                content,
                contentHash: (0, hash_1.sha256)(content),
                mtimeMs: stat.mtimeMs,
                size: stat.size,
                order: order++,
                primary: /agents\.md$/i.test(file),
            };
        }));
    }
    catch (err) {
        diagnostics.push({
            severity: 'warning',
            code: 'RULES_READ_ERROR',
            message: 'Failed reading rule files',
            file: meta.rulerDir,
            detail: err.message,
        });
    }
    const concatenated = (0, RuleProcessor_1.concatenateRules)(ruleFiles.map((f) => ({ path: f.path, content: f.content })), path.dirname(meta.rulerDir));
    const rules = {
        files: ruleFiles,
        concatenated,
        concatenatedHash: (0, hash_1.sha256)(concatenated),
    };
    // Parse TOML MCP servers
    const tomlMcpServers = {};
    if (tomlRaw && typeof tomlRaw === 'object') {
        const tomlObj = tomlRaw;
        if (tomlObj.mcp_servers && typeof tomlObj.mcp_servers === 'object') {
            const mcpServersRaw = tomlObj.mcp_servers;
            for (const [name, def] of Object.entries(mcpServersRaw)) {
                if (!def || typeof def !== 'object')
                    continue;
                const serverDef = def;
                const server = {};
                // Parse command and args
                if (typeof serverDef.command === 'string') {
                    server.command = serverDef.command;
                }
                if (Array.isArray(serverDef.args)) {
                    server.args = serverDef.args.map(String);
                }
                // Parse env
                if (serverDef.env && typeof serverDef.env === 'object') {
                    server.env = Object.fromEntries(Object.entries(serverDef.env).filter(([, v]) => typeof v === 'string'));
                }
                // Parse URL and headers
                if (typeof serverDef.url === 'string') {
                    server.url = serverDef.url;
                }
                if (serverDef.headers && typeof serverDef.headers === 'object') {
                    server.headers = Object.fromEntries(Object.entries(serverDef.headers).filter(([, v]) => typeof v === 'string'));
                }
                if (typeof serverDef.timeout === 'number') {
                    server.timeout = serverDef.timeout;
                }
                // Validate server configuration
                const hasCommand = !!server.command;
                const hasUrl = !!server.url;
                if (!hasCommand && !hasUrl) {
                    diagnostics.push({
                        severity: 'warning',
                        code: 'MCP_TOML_INVALID_SERVER',
                        message: `MCP server '${name}' must have at least one of command or url`,
                        file: tomlFile,
                    });
                    continue;
                }
                if (hasCommand && hasUrl) {
                    diagnostics.push({
                        severity: 'warning',
                        code: 'MCP_TOML_FIELD_CONFLICT',
                        message: `MCP server '${name}' has both command and url - using url (remote)`,
                        file: tomlFile,
                    });
                }
                if (hasCommand && server.headers) {
                    diagnostics.push({
                        severity: 'warning',
                        code: 'MCP_TOML_FIELD_CONFLICT',
                        message: `MCP server '${name}' has headers with command (should be used with url only)`,
                        file: tomlFile,
                    });
                }
                if (hasUrl && server.env) {
                    diagnostics.push({
                        severity: 'warning',
                        code: 'MCP_TOML_FIELD_CONFLICT',
                        message: `MCP server '${name}' has env with url (should be used with command only)`,
                        file: tomlFile,
                    });
                }
                // Derive type - remote takes precedence if both are present
                if (server.url) {
                    server.type = 'remote';
                }
                else if (server.command) {
                    server.type = 'stdio';
                }
                tomlMcpServers[name] = server;
            }
        }
    }
    // Store TOML MCP servers in toml config
    toml.mcpServers = tomlMcpServers;
    // MCP normalization - merge JSON and TOML
    let mcp = null;
    const mcpFile = path.join(meta.rulerDir, 'mcp.json');
    const jsonMcpServers = {};
    let mcpJsonExists = false;
    // Pre-flight existence check so users see warning even if JSON invalid
    try {
        await fs_1.promises.access(mcpFile);
        mcpJsonExists = true;
        // Warning is handled by apply-engine to avoid duplication
    }
    catch {
        // file not present
    }
    // Add deprecation warning if mcp.json exists (regardless of validity)
    if (mcpJsonExists) {
        meta.mcpFile = mcpFile;
        diagnostics.push({
            severity: 'warning',
            code: 'MCP_JSON_DEPRECATED',
            message: 'mcp.json detected: please migrate MCP servers to ruler.toml [mcp_servers.*] sections',
            file: mcpFile,
        });
    }
    try {
        if (mcpJsonExists) {
            const raw = await fs_1.promises.readFile(mcpFile, 'utf8');
            let parsed;
            try {
                parsed = JSON.parse(raw);
            }
            catch (e) {
                // Lenient fallback: strip comments and trailing commas then retry
                const stripped = raw
                    // strip /* */ comments
                    .replace(/\/\*[\s\S]*?\*\//g, '')
                    // strip // comments
                    .replace(/(^|\s+)\/\/.*$/gm, '$1')
                    // remove trailing commas before } or ]
                    .replace(/,\s*([}\]])/g, '$1');
                try {
                    parsed = JSON.parse(stripped);
                }
                catch {
                    throw e; // rethrow original error for diagnostics
                }
            }
            const parsedObj = parsed;
            const serversRaw = parsedObj.mcpServers ||
                parsedObj.servers ||
                {};
            if (serversRaw && typeof serversRaw === 'object') {
                for (const [name, def] of Object.entries(serversRaw)) {
                    if (!def || typeof def !== 'object')
                        continue;
                    const server = {};
                    if (typeof def.command === 'string')
                        server.command = def.command;
                    if (Array.isArray(def.command))
                        server.command = def.command[0];
                    if (Array.isArray(def.args))
                        server.args = def.args.map(String);
                    if (def.env && typeof def.env === 'object') {
                        server.env = Object.fromEntries(Object.entries(def.env).filter(([, v]) => typeof v === 'string'));
                    }
                    if (typeof def.url === 'string')
                        server.url = def.url;
                    if (def.headers && typeof def.headers === 'object') {
                        server.headers = Object.fromEntries(Object.entries(def.headers).filter(([, v]) => typeof v === 'string'));
                    }
                    if (typeof def.timeout === 'number') {
                        server.timeout = def.timeout;
                    }
                    // Derive type
                    if (server.url)
                        server.type = 'remote';
                    else if (server.command)
                        server.type = 'stdio';
                    jsonMcpServers[name] = server;
                }
            }
        }
    }
    catch (err) {
        if (mcpJsonExists) {
            diagnostics.push({
                severity: 'warning',
                code: 'MCP_READ_ERROR',
                message: 'Failed to read mcp.json',
                file: mcpFile,
                detail: err.message,
            });
        }
    }
    // Merge servers: start with JSON, overlay TOML (TOML wins per server name)
    const mergedServers = { ...jsonMcpServers, ...tomlMcpServers };
    // Create MCP bundle if we have any servers
    if (Object.keys(mergedServers).length > 0 || mcpJsonExists) {
        mcp = {
            servers: mergedServers,
            raw: mcpJsonExists ? { mcpServers: jsonMcpServers } : {},
            hash: (0, hash_1.sha256)((0, hash_1.stableJson)(mergedServers)),
        };
    }
    const config = {
        meta,
        toml,
        rules,
        mcp,
        agents: {},
        diagnostics,
        hash: '', // placeholder, recompute after agents
    };
    // Agent resolution (basic): enabled set is CLI override or default_agents
    const cliAgents = options.cliAgents && options.cliAgents.length > 0
        ? options.cliAgents
        : undefined;
    const enabledList = cliAgents ?? toml.defaultAgents ?? [];
    for (const name of enabledList) {
        config.agents[name] = {
            identifier: name,
            enabled: true,
            output: {},
            mcp: { enabled: false, strategy: 'merge' },
        };
    }
    // If CLI provided, mark defaults not included as disabled (optional design choice)
    if (cliAgents) {
        for (const name of toml.defaultAgents ?? []) {
            if (!config.agents[name]) {
                config.agents[name] = {
                    identifier: name,
                    enabled: false,
                    output: {},
                    mcp: { enabled: false, strategy: 'merge' },
                };
            }
        }
    }
    // Recompute hash including agents list
    config.hash = (0, hash_1.sha256)((0, hash_1.stableJson)({
        toml: toml.defaultAgents,
        rules: rules.concatenatedHash,
        mcp: mcp ? mcp.hash : null,
        agents: Object.entries(config.agents).map(([k, v]) => [k, v.enabled]),
    }));
    return config;
}
