import { lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
import path from "node:path";
import { getAgentConfig, resolveHookPath } from "./configs.js";
import { hasOwnErrorCode } from "./error-codes.js";
import { assertNoSymbolicLink } from "./path-safety.js";
function requireAgentConfig(agentId) {
    const config = getAgentConfig(agentId);
    if (!config) {
        throw new Error(`No hook configuration found for agent "${agentId}"`);
    }
    return config;
}
function resolveScopedPath(config, agentId, cwd, homeDir, scope) {
    const targetPath = resolveHookPath(config, scope === "project" ? "local" : "global", cwd, homeDir);
    if (!targetPath) {
        throw new Error(`Agent "${agentId}" has no ${scope} hook path`);
    }
    return targetPath;
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isFullyGeneratedFile(filePath) {
    let parsed;
    try {
        parsed = JSON.parse(readFileSync(filePath, "utf8"));
    }
    catch {
        return false;
    }
    if (!isRecord(parsed)) {
        return false;
    }
    if (Object.keys(parsed).some((key) => key !== "hooks")) {
        return false;
    }
    const hooks = parsed.hooks;
    if (!isRecord(hooks)) {
        return false;
    }
    let handlerFound = false;
    for (const groups of Object.values(hooks)) {
        if (!Array.isArray(groups)) {
            return false;
        }
        for (const group of groups) {
            if (!isRecord(group) || !Array.isArray(group.hooks)) {
                return false;
            }
            for (const handler of group.hooks) {
                if (!isRecord(handler)) {
                    return false;
                }
                handlerFound = true;
                const statusMessage = handler.statusMessage;
                if (typeof statusMessage !== "string" ||
                    !statusMessage.startsWith("[generated:poe-code:")) {
                    return false;
                }
            }
        }
    }
    return handlerFound;
}
export function symlinkHooks(sourceAgentId, targetAgentId, cwd, homeDir, scope) {
    const source = requireAgentConfig(sourceAgentId);
    const target = requireAgentConfig(targetAgentId);
    if (source.format !== target.format) {
        throw new Error(`Cannot symlink hook formats "${source.format}" and "${target.format}"; use transformation instead`);
    }
    const targetPath = resolveScopedPath(source, sourceAgentId, cwd, homeDir, sourceAgentId === targetAgentId && scope === "project" ? "user" : scope);
    const symlinkPath = resolveScopedPath(target, targetAgentId, cwd, homeDir, scope);
    const symlinkParent = path.dirname(symlinkPath);
    let replaced = "none";
    let replacedContents;
    assertNoSymbolicLink(symlinkParent, { root: cwd });
    try {
        const existing = lstatSync(symlinkPath);
        if (existing.isSymbolicLink()) {
            if (readlinkSync(symlinkPath) === targetPath) {
                return { symlinkPath, targetPath, replaced };
            }
            unlinkSync(symlinkPath);
            replaced = "stale-symlink";
        }
        else if (existing.isFile() && isFullyGeneratedFile(symlinkPath)) {
            replacedContents = readFileSync(symlinkPath, "utf8");
            unlinkSync(symlinkPath);
            replaced = "generated-file";
        }
        else {
            throw new Error(`Refuse to replace user-authored hook file at ${symlinkPath}`);
        }
    }
    catch (error) {
        if (!hasOwnErrorCode(error, "ENOENT")) {
            throw error;
        }
    }
    try {
        mkdirSync(symlinkParent, { recursive: true });
        assertNoSymbolicLink(symlinkParent, { root: cwd });
        symlinkSync(targetPath, symlinkPath);
    }
    catch (error) {
        restoreGeneratedFile(cwd, symlinkParent, symlinkPath, replacedContents, error);
        throw error;
    }
    return { symlinkPath, targetPath, replaced };
}
function restoreGeneratedFile(cwd, symlinkParent, symlinkPath, contents, originalError) {
    if (contents === undefined) {
        return;
    }
    try {
        assertNoSymbolicLink(symlinkParent, { root: cwd });
        writeFileSync(symlinkPath, contents, { encoding: "utf8", flag: "wx" });
    }
    catch (restoreError) {
        throw new AggregateError([originalError, restoreError], [
            `Hook symlink replacement failed: ${formatUnknownError(originalError)}`,
            `Generated hook file restore failed: ${formatUnknownError(restoreError)}`
        ].join(" "));
    }
}
function formatUnknownError(error) {
    if (error instanceof Error && error.message.length > 0) {
        return error.message;
    }
    return String(error);
}
