/**
 * Everything Claude Code (ECC) Plugin Hooks for OpenCode
 *
 * This plugin translates Claude Code hooks to OpenCode's plugin system.
 * OpenCode's plugin system is MORE sophisticated than Claude Code with 20+ events
 * compared to Claude Code's 3 phases (PreToolUse, PostToolUse, Stop).
 *
 * Hook Event Mapping:
 * - PreToolUse → tool.execute.before
 * - PostToolUse → tool.execute.after
 * - Stop → session.idle / session.status
 * - SessionStart → session.created
 * - SessionEnd → session.deleted
 */
import * as fs from "fs";
import * as path from "path";
import { initStore, recordChange, clearChanges, } from "./lib/changed-files-store.js";
import changedFilesTool from "../tools/changed-files.js";
export const ECCHooksPlugin = async ({ client, $, directory, worktree, }) => {
    const worktreePath = worktree || directory;
    initStore(worktreePath);
    const editedFiles = new Set();
    function resolvePath(p) {
        if (path.isAbsolute(p))
            return p;
        return path.join(worktreePath, p);
    }
    function hasProjectFile(relativePath) {
        try {
            return fs.statSync(resolvePath(relativePath)).isFile();
        }
        catch {
            return false;
        }
    }
    const pendingToolChanges = new Map();
    let writeCounter = 0;
    function getFilePath(args) {
        if (!args)
            return null;
        const p = (args.filePath ?? args.file_path ?? args.path);
        return typeof p === "string" && p.trim() ? p : null;
    }
    // Helper to call the SDK's log API with correct signature
    const log = (level, message) => client.app.log({ body: { service: "ecc", level, message } });
    const normalizeProfile = (value) => {
        if (value === "minimal" || value === "strict")
            return value;
        return "standard";
    };
    const currentProfile = normalizeProfile(process.env.ECC_HOOK_PROFILE);
    const disabledHooks = new Set((process.env.ECC_DISABLED_HOOKS || "")
        .split(",")
        .map((item) => item.trim())
        .filter(Boolean));
    const profileOrder = {
        minimal: 0,
        standard: 1,
        strict: 2,
    };
    const profileAllowed = (required) => {
        if (Array.isArray(required)) {
            return required.some((entry) => profileOrder[currentProfile] >= profileOrder[entry]);
        }
        return profileOrder[currentProfile] >= profileOrder[required];
    };
    const hookEnabled = (hookId, requiredProfile = "standard") => {
        if (disabledHooks.has(hookId))
            return false;
        return profileAllowed(requiredProfile);
    };
    return {
        /**
         * Prettier Auto-Format Hook
         * Equivalent to Claude Code PostToolUse hook for prettier
         *
         * Triggers: After any JS/TS/JSX/TSX file is edited
         * Action: Runs prettier --write on the file
         */
        "file.edited": async (event) => {
            editedFiles.add(event.path);
            recordChange(event.path, "modified");
            // Auto-format JS/TS files
            if (hookEnabled("post:edit:format", ["strict"]) && event.path.match(/\.(ts|tsx|js|jsx)$/)) {
                try {
                    await $ `prettier --write ${event.path} 2>/dev/null`;
                    log("info", `[ECC] Formatted: ${event.path}`);
                }
                catch {
                    // Prettier not installed or failed - silently continue
                }
            }
            // Console.log warning check
            if (hookEnabled("post:edit:console-warn", ["standard", "strict"]) && event.path.match(/\.(ts|tsx|js|jsx)$/)) {
                try {
                    const result = await $ `grep -n "console\\.log" ${event.path} 2>/dev/null`.text();
                    if (result.trim()) {
                        const lines = result.trim().split("\n").length;
                        log("warn", `[ECC] console.log found in ${event.path} (${lines} occurrence${lines > 1 ? "s" : ""})`);
                    }
                }
                catch {
                    // No console.log found (grep returns non-zero) - this is good
                }
            }
        },
        /**
         * TypeScript Check Hook
         * Equivalent to Claude Code PostToolUse hook for tsc
         *
         * Triggers: After edit tool completes on .ts/.tsx files
         * Action: Runs tsc --noEmit to check for type errors
         */
        "tool.execute.after": async (input, output) => {
            const filePath = getFilePath(input.args);
            if (input.tool === "edit" && filePath) {
                recordChange(filePath, "modified");
            }
            if (input.tool === "write" && filePath) {
                const key = input.callID ?? `write-${++writeCounter}-${filePath}`;
                const pending = pendingToolChanges.get(key);
                if (pending) {
                    recordChange(pending.path, pending.type);
                    pendingToolChanges.delete(key);
                }
                else {
                    recordChange(filePath, "modified");
                }
            }
            // Check if a TypeScript file was edited
            if (hookEnabled("post:edit:typecheck", ["strict"]) &&
                input.tool === "edit" &&
                input.args?.filePath?.match(/\.tsx?$/)) {
                try {
                    await $ `npx tsc --noEmit 2>&1`;
                    log("info", "[ECC] TypeScript check passed");
                }
                catch (error) {
                    const err = error;
                    log("warn", "[ECC] TypeScript errors detected:");
                    if (err.stdout) {
                        // Log first few errors
                        const errors = err.stdout.split("\n").slice(0, 5);
                        errors.forEach((line) => log("warn", `  ${line}`));
                    }
                }
            }
            // PR creation logging
            if (hookEnabled("post:bash:pr-created", ["standard", "strict"]) &&
                input.tool === "bash" &&
                input.args?.toString().includes("gh pr create")) {
                log("info", "[ECC] PR created - check GitHub Actions status");
            }
        },
        /**
         * Pre-Tool Security Check
         * Equivalent to Claude Code PreToolUse hook
         *
         * Triggers: Before tool execution
         * Action: Warns about potential security issues
         */
        "tool.execute.before": async (input) => {
            if (input.tool === "write") {
                const filePath = getFilePath(input.args);
                if (filePath) {
                    const absPath = resolvePath(filePath);
                    let type = "modified";
                    try {
                        if (typeof fs.existsSync === "function") {
                            type = fs.existsSync(absPath) ? "modified" : "added";
                        }
                    }
                    catch {
                        type = "modified";
                    }
                    const key = input.callID ?? `write-${++writeCounter}-${filePath}`;
                    pendingToolChanges.set(key, { path: filePath, type });
                }
            }
            // Git push review reminder
            if (hookEnabled("pre:bash:git-push-reminder", "strict") &&
                input.tool === "bash" &&
                input.args?.toString().includes("git push")) {
                log("info", "[ECC] Remember to review changes before pushing: git diff origin/main...HEAD");
            }
            // Block creation of unnecessary documentation files
            if (hookEnabled("pre:write:doc-file-warning", ["standard", "strict"]) &&
                input.tool === "write" &&
                input.args?.filePath &&
                typeof input.args.filePath === "string") {
                const filePath = input.args.filePath;
                if (filePath.match(/\.(md|txt)$/i) &&
                    !filePath.includes("README") &&
                    !filePath.includes("CHANGELOG") &&
                    !filePath.includes("LICENSE") &&
                    !filePath.includes("CONTRIBUTING")) {
                    log("warn", `[ECC] Creating ${filePath} - consider if this documentation is necessary`);
                }
            }
            // Long-running command reminder
            if (hookEnabled("pre:bash:tmux-reminder", "strict") && input.tool === "bash") {
                const cmd = String(input.args?.command || input.args || "");
                if (cmd.match(/^(npm|pnpm|yarn|bun)\s+(install|build|test|run)/) ||
                    cmd.match(/^cargo\s+(build|test|run)/) ||
                    cmd.match(/^go\s+(build|test|run)/)) {
                    log("info", "[ECC] Long-running command detected - consider using background execution");
                }
            }
        },
        /**
         * Session Created Hook
         * Equivalent to Claude Code SessionStart hook
         *
         * Triggers: When a new session starts
         * Action: Loads context and displays welcome message
         */
        "session.created": async () => {
            if (!hookEnabled("session:start", ["minimal", "standard", "strict"]))
                return;
            log("info", `[ECC] Session started - profile=${currentProfile}`);
            // Check for project-specific context files
            if (hasProjectFile("CLAUDE.md")) {
                log("info", "[ECC] Found CLAUDE.md - loading project context");
            }
        },
        /**
         * Session Idle Hook
         * Equivalent to Claude Code Stop hook
         *
         * Triggers: When session becomes idle (task completed)
         * Action: Runs console.log audit on all edited files
         */
        "session.idle": async () => {
            if (!hookEnabled("stop:check-console-log", ["minimal", "standard", "strict"]))
                return;
            if (editedFiles.size === 0)
                return;
            log("info", "[ECC] Session idle - running console.log audit");
            let totalConsoleLogCount = 0;
            const filesWithConsoleLogs = [];
            for (const file of editedFiles) {
                if (!file.match(/\.(ts|tsx|js|jsx)$/))
                    continue;
                try {
                    const result = await $ `grep -c "console\\.log" ${file} 2>/dev/null`.text();
                    const count = parseInt(result.trim(), 10);
                    if (count > 0) {
                        totalConsoleLogCount += count;
                        filesWithConsoleLogs.push(file);
                    }
                }
                catch {
                    // No console.log found
                }
            }
            if (totalConsoleLogCount > 0) {
                log("warn", `[ECC] Audit: ${totalConsoleLogCount} console.log statement(s) in ${filesWithConsoleLogs.length} file(s)`);
                filesWithConsoleLogs.forEach((f) => log("warn", `  - ${f}`));
                log("warn", "[ECC] Remove console.log statements before committing");
            }
            else {
                log("info", "[ECC] Audit passed: No console.log statements found");
            }
            // Desktop notification (macOS)
            try {
                await $ `osascript -e 'display notification "Task completed!" with title "OpenCode ECC"' 2>/dev/null`;
            }
            catch {
                // Notification not supported or failed
            }
            // Clear tracked files for next task
            editedFiles.clear();
        },
        /**
         * Session Deleted Hook
         * Equivalent to Claude Code SessionEnd hook
         *
         * Triggers: When session ends
         * Action: Final cleanup and state saving
         */
        "session.deleted": async () => {
            if (!hookEnabled("session:end-marker", ["minimal", "standard", "strict"]))
                return;
            log("info", "[ECC] Session ended - cleaning up");
            editedFiles.clear();
            clearChanges();
            pendingToolChanges.clear();
        },
        /**
         * File Watcher Hook
         * OpenCode-only feature
         *
         * Triggers: When file system changes are detected
         * Action: Updates tracking
         */
        "file.watcher.updated": async (event) => {
            let changeType = "modified";
            if (event.type === "create" || event.type === "add")
                changeType = "added";
            else if (event.type === "delete" || event.type === "remove")
                changeType = "deleted";
            recordChange(event.path, changeType);
            if (event.type === "change" && event.path.match(/\.(ts|tsx|js|jsx)$/)) {
                editedFiles.add(event.path);
            }
        },
        /**
         * Todo Updated Hook
         * OpenCode-only feature
         *
         * Triggers: When todo list is updated
         * Action: Logs progress
         */
        "todo.updated": async (event) => {
            const completed = event.todos.filter((t) => t.done).length;
            const total = event.todos.length;
            if (total > 0) {
                log("info", `[ECC] Progress: ${completed}/${total} tasks completed`);
            }
        },
        /**
         * Shell Environment Hook
         * OpenCode-specific: Inject environment variables into shell commands
         *
         * Triggers: Before shell command execution
         * Action: Sets PROJECT_ROOT, PACKAGE_MANAGER, DETECTED_LANGUAGES, ECC_VERSION
         */
        "shell.env": async () => {
            const env = {
                ECC_VERSION: "1.8.0",
                ECC_PLUGIN: "true",
                ECC_HOOK_PROFILE: currentProfile,
                ECC_DISABLED_HOOKS: process.env.ECC_DISABLED_HOOKS || "",
                PROJECT_ROOT: worktreePath,
            };
            // Detect package manager
            const lockfiles = {
                "bun.lockb": "bun",
                "pnpm-lock.yaml": "pnpm",
                "yarn.lock": "yarn",
                "package-lock.json": "npm",
            };
            for (const [lockfile, pm] of Object.entries(lockfiles)) {
                if (hasProjectFile(lockfile)) {
                    env.PACKAGE_MANAGER = pm;
                    break;
                }
            }
            // Detect languages
            const langDetectors = {
                "tsconfig.json": "typescript",
                "go.mod": "go",
                "pyproject.toml": "python",
                "Cargo.toml": "rust",
                "Package.swift": "swift",
            };
            const detected = [];
            for (const [file, lang] of Object.entries(langDetectors)) {
                if (hasProjectFile(file)) {
                    detected.push(lang);
                }
            }
            if (detected.length > 0) {
                env.DETECTED_LANGUAGES = detected.join(",");
                env.PRIMARY_LANGUAGE = detected[0];
            }
            return env;
        },
        /**
         * Session Compacting Hook
         * OpenCode-specific: Control context compaction behavior
         *
         * Triggers: Before context compaction
         * Action: Push ECC context block and custom compaction prompt
         */
        "experimental.session.compacting": async () => {
            const contextBlock = [
                "# ECC Context (preserve across compaction)",
                "",
                "## Active Plugin: Everything Claude Code v2.0.0-rc.1",
                "- Hooks: file.edited, tool.execute.before/after, session.created/idle/deleted, shell.env, compacting, permission.ask",
                "- Tools: run-tests, check-coverage, security-audit, format-code, lint-check, git-summary, changed-files",
                "- Agents: 13 specialized (planner, architect, tdd-guide, code-reviewer, security-reviewer, build-error-resolver, e2e-runner, refactor-cleaner, doc-updater, go-reviewer, go-build-resolver, database-reviewer, python-reviewer)",
                "",
                "## Key Principles",
                "- TDD: write tests first, 80%+ coverage",
                "- Immutability: never mutate, always return new copies",
                "- Security: validate inputs, no hardcoded secrets",
                "",
            ];
            // Include recently edited files
            if (editedFiles.size > 0) {
                contextBlock.push("## Recently Edited Files");
                for (const f of editedFiles) {
                    contextBlock.push(`- ${f}`);
                }
                contextBlock.push("");
            }
            return {
                context: contextBlock.join("\n"),
                compaction_prompt: "Focus on preserving: 1) Current task status and progress, 2) Key decisions made, 3) Files created/modified, 4) Remaining work items, 5) Any security concerns flagged. Discard: verbose tool outputs, intermediate exploration, redundant file listings.",
            };
        },
        /**
         * Permission Auto-Approve Hook
         * OpenCode-specific: Auto-approve safe operations
         *
         * Triggers: When permission is requested
         * Action: Auto-approve reads, formatters, and test commands; log all for audit
         */
        "permission.ask": async (event) => {
            log("info", `[ECC] Permission requested for: ${event.tool}`);
            const cmd = String(event.args?.command || event.args || "");
            // Auto-approve: read/search tools
            if (["read", "glob", "grep", "search", "list"].includes(event.tool)) {
                return { approved: true, reason: "Read-only operation" };
            }
            // Auto-approve: formatters
            if (event.tool === "bash" && /^(npx )?(prettier|biome|black|gofmt|rustfmt|swift-format)/.test(cmd)) {
                return { approved: true, reason: "Formatter execution" };
            }
            // Auto-approve: test execution
            if (event.tool === "bash" && /^(npm test|npx vitest|npx jest|pytest|go test|cargo test)/.test(cmd)) {
                return { approved: true, reason: "Test execution" };
            }
            // Everything else: let user decide
            return { approved: undefined };
        },
        tool: {
            "changed-files": changedFilesTool,
        },
    };
};
export default ECCHooksPlugin;
//# sourceMappingURL=ecc-hooks.js.map