import { z } from "zod";
import { SUPERBLOCKS_LIVE_GIT_BRANCH } from "@superblocksteam/shared";
import { GitAuthError, GitNonFastForwardError, GitRemoteError, classifyGitError, } from "../../../../git-service/index.js";
import { createToolFactory, PermissionLevel, ToolCategory, ToolExecutionError, } from "../types.js";
const READ_ONLY_GIT_SUBCOMMANDS = new Set([
    "blame",
    "cat-file",
    "describe",
    "diff",
    "grep",
    "log",
    "ls-files",
    "ls-remote",
    "ls-tree",
    "rev-list",
    "rev-parse",
    "show",
    "show-branch",
    "status",
]);
const SAFE_MUTATING_GIT_SUBCOMMANDS = new Set([
    "add",
    "commit",
    "fetch",
    "pull",
]);
const DISALLOWED_GIT_GLOBAL_OPTIONS = new Set([
    "-C",
    "-c",
    "--config-env",
    "--exec-path",
    "--git-dir",
    "--namespace",
    "--work-tree",
]);
const DISALLOWED_GIT_SUBCOMMANDS = new Set([
    "bisect",
    "branch",
    "citool",
    "checkout",
    "credential-cache--daemon",
    "daemon",
    "difftool",
    "gui",
    "help",
    "instaweb",
    "mergetool",
    "push",
    "rebase",
    "send-email",
    "switch",
    "web--browse",
    "worktree",
]);
const DISALLOWED_INTERACTIVE_GIT_FLAGS_BY_SUBCOMMAND = new Map([
    ["add", new Set(["-i", "-p", "--interactive", "--patch"])],
    ["clean", new Set(["-i"])],
    ["commit", new Set(["-p", "--interactive", "--patch"])],
    ["reset", new Set(["-p", "--patch"])],
    ["restore", new Set(["-p", "--patch"])],
    ["stash", new Set(["-p", "--patch"])],
]);
const SAFE_GIT_SUBCOMMAND_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
const gitRawInputSchema = z
    .object({
    args: z
        .array(z.string().min(1))
        .min(1)
        .describe("Git argv to run, without the leading 'git'. Example: ['status', '--short']."),
})
    .superRefine((data, ctx) => {
    const [subcommand, ...rest] = data.args;
    if (!subcommand) {
        return;
    }
    if (subcommand === "git") {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["args"],
            message: "Do not include the leading 'git'. Pass argv only.",
        });
        return;
    }
    if (subcommand.startsWith("-")) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["args", 0],
            message: DISALLOWED_GIT_GLOBAL_OPTIONS.has(subcommand)
                ? `Global git option '${subcommand}' is not allowed in git_raw. Start with a git subcommand like 'status' or 'branch'.`
                : "Global git options are not allowed. Start with a git subcommand like 'status' or 'branch'.",
        });
    }
    if (!SAFE_GIT_SUBCOMMAND_PATTERN.test(subcommand)) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["args", 0],
            message: "The first argument must be a single git subcommand token without spaces or shell metacharacters.",
        });
    }
    if (DISALLOWED_GIT_SUBCOMMANDS.has(subcommand)) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["args", 0],
            message: `The git subcommand '${subcommand}' is not allowed in git_raw.`,
        });
    }
    const disallowedInteractiveFlags = DISALLOWED_INTERACTIVE_GIT_FLAGS_BY_SUBCOMMAND.get(subcommand);
    rest.forEach((arg, index) => {
        if (disallowedInteractiveFlags?.has(arg)) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                path: ["args", index + 1],
                message: `Interactive git flag '${arg}' is not allowed in git_raw.`,
            });
        }
    });
    if (rest.includes("--")) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["args"],
            message: "The '--' separator is not allowed in git_raw. Use structured git tools for file-targeted operations.",
        });
    }
});
function getActionNameForArgs(input) {
    const subcommand = input?.args?.[0];
    const subcommand2 = input?.args?.[1];
    switch (subcommand) {
        case "commit":
            return {
                future: "commit and push changes",
                present: "committing and pushing changes",
                past: "committed and pushed changes",
            };
        case "add":
            return {
                future: "stage files",
                present: "staging files",
                past: "staged files",
            };
        case "pull":
            return {
                future: "pull latest changes from remote",
                present: "pulling latest changes from remote",
                past: "pulled latest changes from remote",
            };
        case "fetch":
            return {
                future: "fetch latest changes from remote",
                present: "fetching latest changes from remote",
                past: "fetched latest changes from remote",
            };
        case "merge":
            if (subcommand2 === "--abort") {
                return {
                    future: "abort merge",
                    present: "aborting merge",
                    past: "aborted merge",
                };
            }
            break;
        case "cherry-pick":
            return {
                future: "cherry-pick commits",
                present: "cherry-picking commits",
                past: "cherry-picked commits",
            };
        case "rebase":
            return {
                future: "rebase commits",
                present: "rebasing commits",
                past: "rebased commits",
            };
        case "reset":
            return {
                future: "reset git state",
                present: "resetting git state",
                past: "reset git state",
            };
    }
    if (!subcommand) {
        return {
            future: "run git command",
            present: "running git command",
            past: "ran raw command",
        };
    }
    return {
        future: `run git ${subcommand}`,
        present: `running git ${subcommand}`,
        past: `ran git ${subcommand}`,
    };
}
function quoteGitArg(arg) {
    return /^[A-Za-z0-9._/:=-]+$/.test(arg) ? arg : JSON.stringify(arg);
}
function formatGitCommand(args) {
    return ["git", ...args].map(quoteGitArg).join(" ");
}
function isTagListCommand(args) {
    return (args[0] === "tag" &&
        (args.length === 1 || args.includes("-l") || args.includes("--list")));
}
function isStashReadOnlyCommand(args) {
    return args[0] === "stash" && (args[1] === "list" || args[1] === "show");
}
async function assertOnLiveBranch(gitService, input) {
    const currentBranch = (await gitService.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
    if (currentBranch !== SUPERBLOCKS_LIVE_GIT_BRANCH) {
        throw new ToolExecutionError(`Raw git write operations are only allowed on the ${SUPERBLOCKS_LIVE_GIT_BRANCH} branch.`, {
            toolName: "git_raw",
            input,
            context: {},
            severity: "warn",
        });
    }
}
async function pushLiveBranchWithAutoRecreate(gitService) {
    let remoteExists = null;
    try {
        const refs = await gitService.raw([
            "ls-remote",
            "--heads",
            "origin",
            `refs/heads/${SUPERBLOCKS_LIVE_GIT_BRANCH}`,
        ]);
        remoteExists = refs.includes(`refs/heads/${SUPERBLOCKS_LIVE_GIT_BRANCH}`);
    }
    catch {
        // Continue with normal push if we cannot check remote refs.
    }
    try {
        await gitService.push("origin", SUPERBLOCKS_LIVE_GIT_BRANCH);
    }
    catch (pushError) {
        if (remoteExists === false) {
            await gitService.raw([
                "push",
                "origin",
                `HEAD:${SUPERBLOCKS_LIVE_GIT_BRANCH}`,
            ]);
            return;
        }
        throw pushError;
    }
}
export function isGitRawReadOnlyCommand(args) {
    if (!args || args.length === 0) {
        return false;
    }
    const subcommand = args[0];
    if (!subcommand) {
        return false;
    }
    return (READ_ONLY_GIT_SUBCOMMANDS.has(subcommand) ||
        isTagListCommand(args) ||
        isStashReadOnlyCommand(args));
}
export function isGitRawSafeWriteCommand(args) {
    if (!args || args.length === 0) {
        return false;
    }
    return SAFE_MUTATING_GIT_SUBCOMMANDS.has(args[0]);
}
export function isGitRawWriteCommand(args) {
    if (!args || args.length === 0) {
        return false;
    }
    return !isGitRawReadOnlyCommand(args) && args[0] !== "fetch";
}
export function isGitRawDestructiveCommand(args) {
    if (!args || args.length === 0) {
        return false;
    }
    const [subcommand, ...rest] = args;
    switch (subcommand) {
        case "branch":
            return rest.some((arg) => arg === "-d" || arg === "-D" || arg === "--delete");
        case "tag":
            return rest.some((arg) => arg === "-d" || arg === "--delete");
        case "reset":
            return rest.includes("--hard");
        case "clean":
            return rest.some((arg) => arg === "--force" || /^-[^-]*f/.test(arg));
        case "push":
            return rest.some((arg) => arg === "--force" ||
                arg === "--force-with-lease" ||
                arg.startsWith("--force-with-lease=") ||
                arg === "--delete");
        case "rebase":
            return true;
        case "stash":
            return rest[0] === "drop" || rest[0] === "clear";
        case "worktree":
            return rest[0] === "remove" || rest[0] === "prune";
        default:
            return false;
    }
}
export const gitRawToolFactory = createToolFactory("git_raw", ({ gitService, gitOperationLock, onGitOperationComplete, onGitSetupRequired, }) => ({
    description: `Run a raw git argv command for operations not covered by the structured git tool. ` +
        `Pass argv only without the leading 'git'. All mutating operations must happen on ` +
        `${SUPERBLOCKS_LIVE_GIT_BRANCH}. Branch-changing commands and raw push are not allowed. ` +
        `Detached-HEAD workflows such as bisect are not allowed. ` +
        `If you run commit, the tool will automatically push to origin/${SUPERBLOCKS_LIVE_GIT_BRANCH}. `,
    category: ToolCategory.GIT,
    defaultPermission: PermissionLevel.ALLOW,
    readOnly: false,
    inputSchema: gitRawInputSchema,
    getActionName: (input) => getActionNameForArgs(input),
    formatInput: (input) => ({
        targets: [input.args[0] ?? "git"],
        details: formatGitCommand(input.args),
        summary: `Ok if I run ${formatGitCommand(input.args)}?`,
    }),
    execute: async (input) => {
        if (!gitService) {
            await onGitSetupRequired?.();
            return {
                command: formatGitCommand(input.args),
                setupRequired: true,
            };
        }
        const lockResult = gitOperationLock?.tryAcquire();
        if (lockResult && !lockResult.acquired) {
            throw new ToolExecutionError("Another git operation is already in progress.", {
                toolName: "git_raw",
                input,
                context: {},
                severity: "warn",
            });
        }
        const releaseLock = lockResult?.acquired ? lockResult.release : undefined;
        try {
            if (isGitRawWriteCommand(input.args)) {
                await assertOnLiveBranch(gitService, input);
            }
            const output = await gitService.raw(input.args);
            let pushed = false;
            let pushError;
            if (input.args[0] === "commit") {
                try {
                    await pushLiveBranchWithAutoRecreate(gitService);
                    pushed = true;
                }
                catch (error) {
                    const classified = classifyGitError(error);
                    if (classified instanceof GitNonFastForwardError ||
                        classified instanceof GitAuthError ||
                        classified instanceof GitRemoteError) {
                        pushError = classified.message;
                    }
                    else {
                        throw error;
                    }
                }
            }
            if (!isGitRawReadOnlyCommand(input.args)) {
                onGitOperationComplete?.();
            }
            return {
                command: formatGitCommand(input.args),
                output,
                ...(input.args[0] === "commit" ? { pushed, pushError } : {}),
            };
        }
        catch (error) {
            const classified = classifyGitError(error);
            if (classified instanceof GitAuthError) {
                throw new ToolExecutionError("Git authentication failed. Please check your git credentials in the repository settings.", {
                    toolName: "git_raw",
                    input,
                    context: {},
                    cause: classified,
                    severity: "warn",
                });
            }
            if (classified instanceof GitRemoteError) {
                throw new ToolExecutionError(`Git remote error: ${classified.message}`, {
                    toolName: "git_raw",
                    input,
                    context: {},
                    cause: classified,
                    severity: "warn",
                });
            }
            throw error;
        }
        finally {
            releaseLock?.();
        }
    },
}));
//# sourceMappingURL=git-raw.js.map