import { existsSync, mkdirSync, readFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { getHookPointContract } from '@pellux/goodvibes-sdk/platform/hooks/contracts';
import { matchesEventPath, matchesMatcher } from '@pellux/goodvibes-sdk/platform/hooks/matcher';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
const EMPTY_CONFIG = Object.freeze({ hooks: {}, chains: [] });
function cloneConfig(config) {
    return {
        hooks: Object.fromEntries(Object.entries(config.hooks ?? {}).map(([pattern, defs]) => [pattern, defs.map((def) => ({ ...def }))])),
        chains: (config.chains ?? []).map((chain) => ({
            ...chain,
            steps: chain.steps.map((step) => ({ ...step })),
            action: { ...chain.action },
        })),
    };
}
function ensureConfigShape(parsed) {
    if (!parsed || typeof parsed !== 'object')
        return cloneConfig(EMPTY_CONFIG);
    const candidate = parsed;
    return {
        hooks: candidate.hooks && typeof candidate.hooks === 'object' ? candidate.hooks : {},
        chains: Array.isArray(candidate.chains) ? candidate.chains : [],
    };
}
export class HookWorkbench {
    hookDispatcher;
    hooksFilePathResolver;
    managedConfig = cloneConfig(EMPTY_CONFIG);
    recentActions = [];
    lastSimulation = null;
    lastLoadedPath = null;
    constructor(hookDispatcher, hooksFilePathResolver) {
        this.hookDispatcher = hookDispatcher;
        this.hooksFilePathResolver = hooksFilePathResolver;
    }
    getHooksFilePath() {
        return this.hooksFilePathResolver();
    }
    getManagedConfig() {
        return cloneConfig(this.managedConfig);
    }
    listManagedHooks() {
        const entries = [];
        for (const [pattern, defs] of Object.entries(this.managedConfig.hooks ?? {})) {
            for (const hook of defs)
                entries.push({ pattern, hook: { ...hook } });
        }
        return entries;
    }
    listManagedChains() {
        return (this.managedConfig.chains ?? []).map((chain) => ({
            ...chain,
            steps: chain.steps.map((step) => ({ ...step })),
            action: { ...chain.action },
        }));
    }
    listRecentActions(limit = 8) {
        return this.recentActions.slice(0, limit);
    }
    getLastSimulation() {
        return this.lastSimulation;
    }
    recordAction(action) {
        this.recentActions.unshift(action);
        if (this.recentActions.length > 25)
            this.recentActions.length = 25;
    }
    loadManagedConfig(path = this.getHooksFilePath()) {
        this.lastLoadedPath = path;
        if (!existsSync(path)) {
            this.managedConfig = cloneConfig(EMPTY_CONFIG);
            this.recordAction({ kind: 'load', target: path, timestamp: Date.now(), detail: 'empty config' });
            return this.getManagedConfig();
        }
        try {
            const parsed = JSON.parse(readFileSync(path, 'utf-8'));
            this.managedConfig = ensureConfigShape(parsed);
            this.recordAction({ kind: 'load', target: path, timestamp: Date.now(), detail: `${this.listManagedHooks().length} hooks / ${this.listManagedChains().length} chains` });
        }
        catch (error) {
            logger.debug('HookWorkbench.loadManagedConfig failed, using empty config', { path, error: summarizeError(error) });
            this.managedConfig = cloneConfig(EMPTY_CONFIG);
            this.recordAction({ kind: 'load', target: path, timestamp: Date.now(), detail: 'invalid JSON; using empty config' });
        }
        return this.getManagedConfig();
    }
    async saveManagedConfig(path = this.lastLoadedPath ?? this.getHooksFilePath()) {
        mkdirSync(dirname(path), { recursive: true });
        await writeFile(path, `${JSON.stringify(this.managedConfig, null, 2)}\n`, 'utf-8');
        this.lastLoadedPath = path;
        this.recordAction({ kind: 'save', target: path, timestamp: Date.now(), detail: `${this.listManagedHooks().length} hooks / ${this.listManagedChains().length} chains` });
    }
    async loadAndApplyManagedHooks(path = this.getHooksFilePath()) {
        this.loadManagedConfig(path);
        this.hookDispatcher.clear();
        this.hookDispatcher.loadFromFile(path);
        this.recordAction({ kind: 'reload', target: path, timestamp: Date.now(), detail: 'dispatcher refreshed' });
    }
    scaffoldHook(name, match, type) {
        const hook = {
            name,
            match,
            type,
            enabled: false,
            description: `Managed hook scaffold for ${match}`,
            ...(type === 'command' ? { command: 'echo implement-hook' } : {}),
            ...(type === 'prompt' ? { prompt: 'Return a HookResult JSON object for $ARGUMENTS.' } : {}),
            ...(type === 'http' ? { url: 'http://localhost:3000/hooks' } : {}),
            ...(type === 'ts' ? { path: './hooks/handler.ts' } : {}),
            ...(type === 'agent' ? { prompt: 'Handle this hook event: $ARGUMENTS' } : {}),
        };
        const config = cloneConfig(this.managedConfig);
        config.hooks ??= {};
        config.hooks[match] ??= [];
        config.hooks[match].push(hook);
        this.managedConfig = config;
        this.recordAction({ kind: 'scaffold-hook', target: name, timestamp: Date.now(), detail: `${type} ${match}` });
        return hook;
    }
    scaffoldChain(name, matches) {
        const chain = {
            name,
            description: `Managed hook workflow for ${matches.join(' -> ')}`,
            steps: matches.map((match) => ({ match })),
            action: {
                name: `${name}-action`,
                match: matches[matches.length - 1] ?? 'Post:tool:*',
                type: 'command',
                command: 'echo chain-action',
                enabled: true,
            },
        };
        const config = cloneConfig(this.managedConfig);
        config.chains ??= [];
        config.chains.push(chain);
        this.managedConfig = config;
        this.recordAction({ kind: 'scaffold-chain', target: name, timestamp: Date.now(), detail: matches.join(' -> ') });
        return chain;
    }
    removeManagedEntry(name) {
        const config = cloneConfig(this.managedConfig);
        let removed = false;
        for (const [pattern, defs] of Object.entries(config.hooks ?? {})) {
            const nextDefs = defs.filter((def) => def.name !== name);
            if (nextDefs.length !== defs.length) {
                removed = true;
                if (nextDefs.length === 0)
                    delete config.hooks?.[pattern];
                else
                    config.hooks[pattern] = nextDefs;
            }
        }
        const nextChains = (config.chains ?? []).filter((chain) => chain.name !== name);
        if (nextChains.length !== (config.chains ?? []).length) {
            removed = true;
            config.chains = nextChains;
        }
        if (removed) {
            this.managedConfig = config;
            this.recordAction({ kind: 'remove', target: name, timestamp: Date.now() });
        }
        return removed;
    }
    toggleManagedHook(name, enabled) {
        let changed = false;
        const config = cloneConfig(this.managedConfig);
        for (const defs of Object.values(config.hooks ?? {})) {
            for (const hook of defs) {
                if (hook.name === name) {
                    hook.enabled = enabled;
                    changed = true;
                }
            }
        }
        if (changed) {
            this.managedConfig = config;
            this.recordAction({ kind: 'toggle', target: name, timestamp: Date.now(), detail: enabled ? 'enabled' : 'disabled' });
        }
        return changed;
    }
    async exportManagedConfig(path) {
        await this.saveManagedConfig(path);
        this.recordAction({ kind: 'export', target: path, timestamp: Date.now() });
        return path;
    }
    inspectManagedConfig(path) {
        const config = ensureConfigShape(JSON.parse(readFileSync(path, 'utf-8')));
        const inspection = {
            path,
            hookCount: Object.values(config.hooks ?? {}).reduce((sum, defs) => sum + defs.length, 0),
            chainCount: (config.chains ?? []).length,
            patterns: Object.keys(config.hooks ?? {}).sort((a, b) => a.localeCompare(b)),
        };
        this.recordAction({
            kind: 'inspect',
            target: path,
            timestamp: Date.now(),
            detail: `${inspection.hookCount} hooks / ${inspection.chainCount} chains`,
        });
        return inspection;
    }
    importManagedConfig(path, strategy = 'merge') {
        const incoming = ensureConfigShape(JSON.parse(readFileSync(path, 'utf-8')));
        if (strategy === 'replace') {
            this.managedConfig = cloneConfig(incoming);
        }
        else {
            const merged = cloneConfig(this.managedConfig);
            merged.hooks ??= {};
            for (const [pattern, defs] of Object.entries(incoming.hooks ?? {})) {
                const existing = merged.hooks[pattern] ?? [];
                const byName = new Map(existing.map((def) => [def.name ?? `${pattern}:${def.type}:${existing.indexOf(def)}`, { ...def }]));
                for (const def of defs) {
                    byName.set(def.name ?? `${pattern}:${def.type}:${byName.size}`, { ...def });
                }
                merged.hooks[pattern] = [...byName.values()];
            }
            const existingChains = new Map((merged.chains ?? []).map((chain) => [chain.name, {
                    ...chain,
                    steps: chain.steps.map((step) => ({ ...step })),
                    action: { ...chain.action },
                }]));
            for (const chain of incoming.chains ?? []) {
                existingChains.set(chain.name, {
                    ...chain,
                    steps: chain.steps.map((step) => ({ ...step })),
                    action: { ...chain.action },
                });
            }
            merged.chains = [...existingChains.values()];
            this.managedConfig = merged;
        }
        this.recordAction({
            kind: 'import',
            target: path,
            timestamp: Date.now(),
            detail: `${strategy} -> ${this.listManagedHooks().length} hooks / ${this.listManagedChains().length} chains`,
        });
        return this.getManagedConfig();
    }
    simulate(eventPath, payload = {}) {
        const [phase, category, ...specificParts] = eventPath.split(':');
        const specific = specificParts.join(':');
        const event = {
            path: eventPath,
            phase: phase,
            category: category,
            specific,
            sessionId: 'hook-sim',
            timestamp: Date.now(),
            payload,
        };
        const matchedHooks = this.listManagedHooks()
            .filter(({ pattern, hook }) => matchesEventPath(pattern, event.path) && matchesMatcher(hook.matcher, event.specific))
            .map(({ pattern, hook }) => ({
            pattern,
            name: hook.name ?? '(unnamed)',
            type: hook.type,
            contract: getHookPointContract(event.path)?.description ?? 'No exact contract',
        }));
        const matchedChains = this.listManagedChains()
            .map((chain) => ({
            name: chain.name,
            stepMatches: chain.steps.filter((step) => matchesEventPath(step.match, event.path)).length,
        }))
            .filter((chain) => chain.stepMatches > 0);
        const result = {
            eventPath,
            matchedHooks,
            matchedChains,
            capturedAt: Date.now(),
        };
        this.lastSimulation = result;
        this.recordAction({
            kind: 'simulate',
            target: eventPath,
            timestamp: result.capturedAt,
            detail: `${matchedHooks.length} hooks / ${matchedChains.length} chains`,
        });
        return result;
    }
    clear() {
        this.managedConfig = cloneConfig(EMPTY_CONFIG);
        this.recentActions = [];
        this.lastSimulation = null;
        this.lastLoadedPath = null;
    }
}
export function createHookWorkbench(options) {
    const hooksFilePathResolver = options.hooksFilePathResolver ?? (() => {
        const workingDirectory = options.configManager.getWorkingDirectory();
        if (!workingDirectory) {
            throw new Error('createHookWorkbench requires configManager.getWorkingDirectory() when no hooksFilePathResolver is provided');
        }
        return resolve(workingDirectory, options.configManager.get('tools.hooksFile'));
    });
    return new HookWorkbench(options.hookDispatcher, hooksFilePathResolver);
}
