// engine/worker-config-loader.ts — 3-layer configuration merge for workers (SPEC-138)
//
// THE PATTERN (specific to workers):
//   Layer 1 (lowest): defaults from workers-registry.json (loaded separately, passed in)
//   Layer 2: planu/workers.json — project-level overrides (optional, graceful if missing)
//   Layer 3 (highest): PLANU_WORKER_<NAME>_<SETTING> env vars
//
// Env var format:
//   PLANU_WORKER_TEST_GAPS_ENABLED=false
//   PLANU_WORKER_SECURITY_AUDIT_COOLDOWNMS=60000
//   PLANU_WORKER_OPTIMIZATION_PRIORITY=3
//
// Slug-to-env conversion: 'test-gaps' → 'TEST_GAPS' (hyphens → underscores, uppercase)
//
// Zero mutation guarantee: all returned values are new objects.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
/**
 * 3-layer configuration loader specific to workers.
 *
 * Layer 1: registry defaults (passed via load() calls externally)
 * Layer 2: planu/workers.json (project-level overrides)
 * Layer 3: PLANU_WORKER_<NAME>_<SETTING> environment variables
 *
 * Higher-precedence layers win field by field.
 */
export class WorkerConfigLoader {
    projectPath;
    constructor(projectPath) {
        this.projectPath = projectPath;
    }
    /**
     * Load and merge all 3 layers. Returns final resolved override per worker.
     * The returned Map contains only workers that have at least one override.
     * Workers with no overrides in any layer are not present (use defaults as-is).
     *
     * @param workerNames - names of all known workers (to scan env for each)
     */
    async load(workerNames = []) {
        const fileOverrides = await this.loadProjectFile();
        const result = new Map();
        // Collect all relevant worker names (from file + provided list)
        const allNames = new Set([...Object.keys(fileOverrides), ...workerNames]);
        for (const name of allNames) {
            const fileLayer = fileOverrides[name] ?? {};
            const envLayer = this.parseEnvOverride(name);
            const merged = this.merge(fileLayer, envLayer);
            // Only include if there is at least one defined field
            if (Object.keys(merged).length > 0) {
                result.set(name, merged);
            }
        }
        return result;
    }
    /**
     * Get the merged override for a specific worker (layers 2 + 3 only).
     * Returns an empty object if no override is defined anywhere.
     */
    async getOverride(workerName) {
        const fileOverrides = await this.loadProjectFile();
        const fileLayer = fileOverrides[workerName] ?? {};
        const envLayer = this.parseEnvOverride(workerName);
        return this.merge(fileLayer, envLayer);
    }
    // -------------------------------------------------------------------------
    // Private helpers
    // -------------------------------------------------------------------------
    /** Load planu/workers.json. Returns empty object if file doesn't exist. */
    async loadProjectFile() {
        const filePath = join(this.projectPath, 'planu', 'workers.json');
        try {
            const content = await readFile(filePath, 'utf-8');
            const parsed = JSON.parse(content);
            if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
                return {};
            }
            return this.coerceFileContent(parsed);
        }
        catch {
            // File does not exist or is unreadable — graceful fallback
            return {};
        }
    }
    /** Coerce raw file content to Record<string, WorkerOverride>, ignoring unknown keys. */
    coerceFileContent(raw) {
        const result = {};
        for (const [name, value] of Object.entries(raw)) {
            if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
                result[name] = this.coerceOverride(value);
            }
        }
        return result;
    }
    /** Coerce a raw object to WorkerOverride, ignoring unknown keys. */
    coerceOverride(raw) {
        const override = {};
        if (typeof raw.enabled === 'boolean') {
            override.enabled = raw.enabled;
        }
        if (typeof raw.cooldownMs === 'number') {
            override.cooldownMs = raw.cooldownMs;
        }
        if (typeof raw.priority === 'number') {
            override.priority = raw.priority;
        }
        if (raw.tier === 'free' || raw.tier === 'pro') {
            override.tier = raw.tier;
        }
        return override;
    }
    /**
     * Parse environment variables for a specific worker.
     *
     * Slug-to-env: 'test-gaps' → 'TEST_GAPS'
     * Full pattern: PLANU_WORKER_{WORKER_ENV}_{SETTING}
     * Known settings: ENABLED (bool), COOLDOWNMS (number), PRIORITY (number), TIER (string)
     */
    parseEnvOverride(workerName) {
        const envName = workerName.toUpperCase().replace(/-/g, '_');
        const prefix = `PLANU_WORKER_${envName}_`;
        const override = {};
        const enabledRaw = process.env[`${prefix}ENABLED`];
        if (enabledRaw !== undefined) {
            override.enabled = enabledRaw.toLowerCase() === 'true';
        }
        const cooldownRaw = process.env[`${prefix}COOLDOWNMS`];
        if (cooldownRaw !== undefined) {
            const parsed = Number(cooldownRaw);
            if (!Number.isNaN(parsed)) {
                override.cooldownMs = parsed;
            }
        }
        const priorityRaw = process.env[`${prefix}PRIORITY`];
        if (priorityRaw !== undefined) {
            const parsed = Number(priorityRaw);
            if (!Number.isNaN(parsed)) {
                override.priority = parsed;
            }
        }
        const tierRaw = process.env[`${prefix}TIER`];
        if (tierRaw === 'free' || tierRaw === 'pro') {
            override.tier = tierRaw;
        }
        return override;
    }
    /**
     * Merge two override objects. Right-hand side wins for defined fields.
     * Returns a new object — zero mutation guarantee.
     */
    merge(base, override) {
        const result = { ...base };
        if (override.enabled !== undefined) {
            result.enabled = override.enabled;
        }
        if (override.cooldownMs !== undefined) {
            result.cooldownMs = override.cooldownMs;
        }
        if (override.priority !== undefined) {
            result.priority = override.priority;
        }
        if (override.tier !== undefined) {
            result.tier = override.tier;
        }
        return result;
    }
}
//# sourceMappingURL=worker-config-loader.js.map