// engine/hooks/config-loader.ts — Load & validate hook config with live reload (SPEC-129)
import { watch as fsWatch } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { z } from 'zod';
// ---------------------------------------------------------------------------
// Default config (matches hook-defaults.json)
// ---------------------------------------------------------------------------
import hookDefaults from '../../config/hook-defaults.json' with { type: 'json' };
// ---------------------------------------------------------------------------
// Zod schemas
// ---------------------------------------------------------------------------
const hookTypeConfigSchema = z.object({
    enabled: z.boolean().default(true),
    include: z.array(z.string()).optional(),
    exclude: z.array(z.string()).optional(),
});
const fileHookConfigSchema = z.object({
    enabled: z.boolean().default(true),
    debounceMs: z.number().min(50).max(10_000).default(300),
    rateLimitPerFileMs: z.number().min(1000).max(60_000).default(5000),
    hooks: z.record(z.string(), hookTypeConfigSchema).default({}),
});
// ---------------------------------------------------------------------------
// Config file paths
// ---------------------------------------------------------------------------
const HOOKS_JSON = '.planu/hooks.json';
const PLANU_JSON = 'planu.json';
// ---------------------------------------------------------------------------
// ConfigLoader
// ---------------------------------------------------------------------------
export class ConfigLoader {
    config;
    projectRoot;
    watcher = null;
    changeCallbacks = [];
    constructor(projectRoot) {
        this.projectRoot = projectRoot;
        this.config = hookDefaults;
    }
    /**
     * Load config from `.planu/hooks.json` or `planu.json`.
     * Falls back to defaults if file missing or invalid.
     */
    async load() {
        // Try .planu/hooks.json first
        let raw = await this.readJsonFile(join(this.projectRoot, HOOKS_JSON));
        // Try planu.json -> hooks key
        if (!raw) {
            const planuJson = await this.readJsonFile(join(this.projectRoot, PLANU_JSON));
            if (planuJson &&
                typeof planuJson === 'object' &&
                'hooks' in planuJson) {
                raw = planuJson.hooks;
            }
        }
        if (raw) {
            const parsed = fileHookConfigSchema.safeParse(raw);
            if (parsed.success) {
                this.config = parsed.data;
            }
            else {
                const messages = parsed.error.issues.map((i) => i.message).join(', ');
                console.warn(`[hooks] Invalid hook config, using defaults: ${messages}`);
                this.config = hookDefaults;
            }
        }
        else {
            this.config = hookDefaults;
        }
        return this.config;
    }
    /**
     * Get current config (synchronous, must call load() first).
     */
    getConfig() {
        return this.config;
    }
    /**
     * Get config for a specific hook type.
     */
    getHookConfig(hookType) {
        return this.config.hooks[hookType];
    }
    /**
     * Start watching config files for live reload.
     */
    startWatching() {
        const hooksJsonPath = join(this.projectRoot, HOOKS_JSON);
        try {
            this.watcher = fsWatch(hooksJsonPath, () => {
                void this.reloadConfig();
            });
            this.watcher.on('error', () => {
                // Ignore watch errors — config just won't live-reload
            });
        }
        catch {
            // File doesn't exist yet — that's fine
        }
    }
    /**
     * Register a callback for config changes.
     */
    onChange(callback) {
        this.changeCallbacks.push(callback);
    }
    /**
     * Stop watching and clean up.
     */
    close() {
        if (this.watcher) {
            this.watcher.close();
            this.watcher = null;
        }
        this.changeCallbacks = [];
    }
    // ---------------------------------------------------------------------------
    // Private
    // ---------------------------------------------------------------------------
    async reloadConfig() {
        try {
            await this.load();
            for (const cb of this.changeCallbacks) {
                cb(this.config);
            }
        }
        catch {
            // Ignore reload errors — keep previous config
        }
    }
    async readJsonFile(filePath) {
        try {
            const data = await readFile(filePath, 'utf-8');
            return JSON.parse(data);
        }
        catch {
            return null;
        }
    }
}
//# sourceMappingURL=config-loader.js.map