/**
 * Advanced Hook System
 *
 * Extends the basic hook system with Claude Code-like capabilities:
 * - Command hooks: run shell commands, parse JSON stdout for decisions
 * - Prompt hooks: LLM-evaluated hooks (stub - returns allow with stored prompt)
 * - Agent hooks: spawns subagent evaluation (stub - returns allow)
 *
 * Supports event matching with regex matchers, async fire-and-forget,
 * one-shot hooks, and a registry for CRUD operations.
 */
import { spawn } from 'child_process';
import { logger } from '../utils/logger.js';
export var HookEvent;
(function (HookEvent) {
    HookEvent["PreToolUse"] = "PreToolUse";
    HookEvent["PostToolUse"] = "PostToolUse";
    HookEvent["PreBash"] = "PreBash";
    HookEvent["PostBash"] = "PostBash";
    HookEvent["PreEdit"] = "PreEdit";
    HookEvent["PostEdit"] = "PostEdit";
    HookEvent["SessionStart"] = "SessionStart";
    HookEvent["SessionEnd"] = "SessionEnd";
    HookEvent["PreCompact"] = "PreCompact";
    HookEvent["Notification"] = "Notification";
    HookEvent["SubagentStart"] = "SubagentStart";
    HookEvent["SubagentStop"] = "SubagentStop";
    HookEvent["PermissionRequest"] = "PermissionRequest";
    HookEvent["TaskCompleted"] = "TaskCompleted";
})(HookEvent || (HookEvent = {}));
// ============================================================================
// AdvancedHookRunner
// ============================================================================
export class AdvancedHookRunner {
    workingDirectory;
    timeout;
    constructor(workingDirectory = process.cwd(), timeout = 30000) {
        this.workingDirectory = workingDirectory;
        this.timeout = timeout;
    }
    /**
     * Run a hook and return a decision
     */
    async runHook(hook, context) {
        switch (hook.type) {
            case 'command':
                return this.runCommandHook(hook, context);
            case 'prompt':
                return this.runPromptHook(hook, context);
            case 'agent':
                return this.runAgentHook(hook, context);
            default:
                logger.warn(`Unknown hook type: ${hook.type}`, { source: 'AdvancedHookRunner' });
                return { action: 'allow' };
        }
    }
    /**
     * Fire-and-forget async hook execution
     */
    async runHookAsync(hook, context) {
        this.runHook(hook, context).catch((error) => {
            logger.warn(`Async hook "${hook.name}" failed: ${error}`, { source: 'AdvancedHookRunner' });
        });
    }
    /**
     * Check if a hook matches a given event and optional tool name
     */
    matchesEvent(hook, event, toolName) {
        if (hook.event !== event) {
            return false;
        }
        if (hook.matcher && toolName) {
            return hook.matcher.test(toolName);
        }
        // If no matcher, match all tools for that event
        if (!hook.matcher) {
            return true;
        }
        // Has matcher but no toolName provided - no match
        return false;
    }
    async runCommandHook(hook, context) {
        if (!hook.command) {
            logger.warn(`Command hook "${hook.name}" has no command`, { source: 'AdvancedHookRunner' });
            return { action: 'allow' };
        }
        return new Promise((resolve) => {
            let timedOut = false;
            const child = spawn(hook.command, [], {
                cwd: this.workingDirectory,
                shell: true,
                env: {
                    ...process.env,
                    HOOK_EVENT: context.event,
                    HOOK_TOOL: context.toolName || '',
                    HOOK_INPUT: JSON.stringify(context.input || {}),
                },
            });
            let stdout = '';
            let stderr = '';
            const timeoutId = setTimeout(() => {
                timedOut = true;
                child.kill('SIGTERM');
            }, this.timeout);
            child.stdout?.on('data', (data) => {
                stdout += data.toString();
            });
            child.stderr?.on('data', (data) => {
                stderr += data.toString();
            });
            child.on('close', (code) => {
                clearTimeout(timeoutId);
                if (timedOut) {
                    logger.warn(`Command hook "${hook.name}" timed out`, { source: 'AdvancedHookRunner' });
                    resolve({ action: 'allow' });
                    return;
                }
                if (code !== 0) {
                    logger.warn(`Command hook "${hook.name}" exited with code ${code}: ${stderr}`, { source: 'AdvancedHookRunner' });
                    resolve({ action: 'allow' });
                    return;
                }
                try {
                    const decision = JSON.parse(stdout.trim());
                    if (decision.action && ['allow', 'deny', 'ask'].includes(decision.action)) {
                        resolve(decision);
                    }
                    else {
                        logger.warn(`Command hook "${hook.name}" returned invalid action`, { source: 'AdvancedHookRunner' });
                        resolve({ action: 'allow' });
                    }
                }
                catch {
                    // Non-JSON output: if exit code 0, allow; non-zero handled above
                    resolve({ action: 'allow' });
                }
            });
            child.on('error', (error) => {
                clearTimeout(timeoutId);
                logger.warn(`Command hook "${hook.name}" failed: ${error.message}`, { source: 'AdvancedHookRunner' });
                resolve({ action: 'allow' });
            });
        });
    }
    async runPromptHook(hook, _context) {
        // Stub: returns allow with the prompt text stored as additional context
        logger.debug(`Prompt hook "${hook.name}" evaluated (stub)`, { source: 'AdvancedHookRunner' });
        return {
            action: 'allow',
            additionalContext: hook.prompt || undefined,
        };
    }
    async runAgentHook(hook, _context) {
        // Stub: returns allow
        logger.debug(`Agent hook "${hook.name}" evaluated (stub)`, { source: 'AdvancedHookRunner' });
        return { action: 'allow' };
    }
}
// ============================================================================
// HookRegistry
// ============================================================================
export class HookRegistry {
    hooks = new Map();
    firedOnceHooks = new Set();
    /**
     * Add a hook to the registry
     */
    addHook(hook) {
        this.hooks.set(hook.name, hook);
        logger.debug(`Hook "${hook.name}" registered for event ${hook.event}`, { source: 'HookRegistry' });
    }
    /**
     * Remove a hook by name
     */
    removeHook(name) {
        const deleted = this.hooks.delete(name);
        this.firedOnceHooks.delete(name);
        if (deleted) {
            logger.debug(`Hook "${name}" removed`, { source: 'HookRegistry' });
        }
        return deleted;
    }
    /**
     * Get all hooks that match a given event, filtering out already-fired once hooks
     */
    getHooksForEvent(event, toolName) {
        const runner = new AdvancedHookRunner();
        const matching = [];
        for (const hook of this.hooks.values()) {
            // Skip once hooks that already fired
            if (hook.once && this.firedOnceHooks.has(hook.name)) {
                continue;
            }
            if (runner.matchesEvent(hook, event, toolName)) {
                matching.push(hook);
            }
        }
        return matching;
    }
    /**
     * Mark a once hook as fired
     */
    markFired(hookName) {
        const hook = this.hooks.get(hookName);
        if (hook?.once) {
            this.firedOnceHooks.add(hookName);
        }
    }
    /**
     * List all registered hooks
     */
    listHooks() {
        return Array.from(this.hooks.values());
    }
    /**
     * Get a hook by name
     */
    getHook(name) {
        return this.hooks.get(name);
    }
    /**
     * Clear all hooks
     */
    clear() {
        this.hooks.clear();
        this.firedOnceHooks.clear();
    }
    /**
     * Get the number of registered hooks
     */
    get size() {
        return this.hooks.size;
    }
}
// ============================================================================
// Singleton
// ============================================================================
let registryInstance = null;
let runnerInstance = null;
export function getHookRegistry() {
    if (!registryInstance) {
        registryInstance = new HookRegistry();
    }
    return registryInstance;
}
export function getAdvancedHookRunner(workingDirectory) {
    if (!runnerInstance || workingDirectory) {
        runnerInstance = new AdvancedHookRunner(workingDirectory);
    }
    return runnerInstance;
}
export function resetAdvancedHooks() {
    registryInstance = null;
    runnerInstance = null;
}
//# sourceMappingURL=advanced-hooks.js.map