import { CLI_REMEDIATION_DOCS } from "../core/cli-remediation.js";
import { ModuleCommandRouter, ModuleCommandRouterError } from "../core/module-command-router.js";
import { resolveRegistryAndConfig } from "../core/module-registry-resolve.js";
import { appendPolicyTrace, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, AGENT_CLI_MAP_HUMAN_DOC, POLICY_APPROVAL_TWO_LANES_DOC, POLICY_RUN_ENV_LANE_MISMATCH_DETAIL, resolveActorWithFallback, resolvePolicyOperationIdForCommand } from "../core/policy.js";
import { getSessionGrant, recordSessionGrant, resolveSessionId } from "../core/session-policy.js";
import { createKitLifecycleHookBus } from "../core/kit-lifecycle-hooks.js";
import { persistCaeTraceIfEnabled } from "../core/cae/cae-kit-sqlite.js";
import { mergeCaeIntoCommandResult, runCaeCliPreflight } from "../core/cae/cae-run-preflight.js";
import { applyResponseTemplateApplication } from "../core/response-template-shaping.js";
import { getAtPath } from "../core/workspace-kit-config.js";
import { buildRunArgsSchemaOnlyPayload, enforcePlanningGenerationCliPrelude, validatePilotRunCommandArgs } from "../core/run-args-pilot-validation.js";
import { defaultRegistryModules } from "../modules/index.js";
import { TaskEngineError } from "../modules/task-engine/transitions.js";
import { tryAutoCheckpointBeforeRun } from "../modules/checkpoints/checkpoint-auto.js";
import { promptSensitiveRunApproval } from "./interactive-policy.js";
import { releaseTranscriptHookLockFromEnv } from "../core/transcript-completion-hook.js";
import { storeCaeSession } from "../modules/context-activation/trace-store.js";
import { cliDiscoveryEnvelope } from "../core/cli-discovery.js";
import { buildRunCommandCatalogPayload } from "./run-command-catalog.js";
import { createRunInvocationId, emitRunInvocationJson } from "./run-invocation-output.js";
import { peelRunArgv, policyDeniedBody } from "./run-helpers.js";
/** Default apply-skill preview mode for policy (dryRun true when omitted). */
function normalizeApplySkillArgs(args) {
    const next = { ...args };
    const opt = typeof next.options === "object" && next.options !== null && !Array.isArray(next.options)
        ? { ...next.options }
        : {};
    if (!Object.hasOwn(opt, "dryRun")) {
        opt.dryRun = true;
    }
    next.options = opt;
    return next;
}
export async function handleRunCommand(cwd, args, io, codes) {
    const { writeLine, writeError } = io;
    const invocationId = createRunInvocationId();
    const runStartedAt = new Date().toISOString();
    const peeled = peelRunArgv(args.slice(1));
    const outputFileRequest = peeled.outputFile;
    let effectiveForRunLog;
    let commandForRunLog;
    let argsForRunLog = {};
    const emitJson = async (body) => {
        await emitRunInvocationJson(writeLine, cwd, body, {
            invocationId,
            outputFileRequest,
            persistRunLog: commandForRunLog
                ? {
                    effectiveConfig: effectiveForRunLog,
                    command: commandForRunLog,
                    commandArgs: argsForRunLog,
                    startedAt: runStartedAt
                }
                : undefined
        });
    };
    let { jsonCatalog, rest } = peeled;
    let subcommand = rest[0];
    if (subcommand === "list-commands") {
        jsonCatalog = true;
        subcommand = undefined;
    }
    else if (peeled.listCommands) {
        jsonCatalog = true;
    }
    const schemaOnlyFlag = typeof rest[1] === "string" && (rest[1] === "--schema-only" || rest[1] === "-S");
    let commandArgs = {};
    if (subcommand && rest[1] && !schemaOnlyFlag) {
        try {
            const parsed = JSON.parse(rest[1]);
            if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
                commandArgs = parsed;
            }
            else {
                writeError("Command args must be a JSON object.");
                return codes.usageError;
            }
        }
        catch {
            writeError(`Invalid JSON args: ${rest[1]}`);
            return codes.usageError;
        }
    }
    const invocationConfig = typeof commandArgs.config === "object" &&
        commandArgs.config !== null &&
        !Array.isArray(commandArgs.config)
        ? commandArgs.config
        : {};
    let registry;
    let router;
    let effective;
    try {
        const resolved = await resolveRegistryAndConfig(cwd, defaultRegistryModules, invocationConfig);
        registry = resolved.registry;
        effective = resolved.effective;
        effectiveForRunLog = effective;
        router = new ModuleCommandRouter(registry);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        writeError(`Module registry / config resolution failed: ${message}`);
        return codes.validationFailure;
    }
    if (subcommand && schemaOnlyFlag) {
        const descriptor = router.describeCommand(subcommand);
        if (!descriptor) {
            const names = router.listCommands().map((command) => command.name);
            await emitJson({
                ok: false,
                code: "unknown-command",
                message: `Unknown command '${subcommand}'. Run 'workspace-kit run --list-commands' for the command catalog.`,
                details: { availableCommands: names },
                remediation: { docPath: CLI_REMEDIATION_DOCS.agentCliMap },
                discovery: cliDiscoveryEnvelope()
            });
            return codes.validationFailure;
        }
        const payload = buildRunArgsSchemaOnlyPayload(subcommand, {
            name: descriptor.name,
            moduleId: descriptor.moduleId,
            instructionPath: descriptor.instructionFile,
            description: descriptor.description
        });
        if (!payload) {
            await emitJson({
                ok: false,
                code: "schema-only-unsupported",
                message: `Command '${subcommand}' is executable but has no schema-only metadata. This is a command contract bug.`,
                remediation: {
                    instructionPath: "src/modules/task-engine/instructions/agent-mutation-plan.md",
                    docPath: CLI_REMEDIATION_DOCS.agentCliMap
                },
                discovery: cliDiscoveryEnvelope()
            });
            return codes.validationFailure;
        }
        await emitJson({ ...payload, discovery: cliDiscoveryEnvelope() });
        return codes.success;
    }
    if (subcommand) {
        const pilotErr = validatePilotRunCommandArgs(subcommand, commandArgs, effective);
        if (pilotErr) {
            await emitJson(pilotErr);
            return codes.validationFailure;
        }
        const planPrelude = enforcePlanningGenerationCliPrelude(subcommand, commandArgs, effective);
        if (planPrelude) {
            await emitJson(planPrelude);
            return codes.validationFailure;
        }
    }
    if (subcommand === "apply-skill") {
        commandArgs = normalizeApplySkillArgs(commandArgs);
    }
    if (subcommand) {
        commandForRunLog = subcommand;
        argsForRunLog = commandArgs;
    }
    if (!subcommand && jsonCatalog) {
        await emitJson({
            ...buildRunCommandCatalogPayload(router, effective),
            discovery: cliDiscoveryEnvelope()
        });
        return codes.success;
    }
    if (!subcommand) {
        const commands = router.listCommands();
        writeLine("Available module commands:");
        for (const cmd of commands) {
            const desc = cmd.description ? ` — ${cmd.description}` : "";
            writeLine(`  ${cmd.name} (${cmd.moduleId})${desc}`);
        }
        writeLine("");
        writeLine(`Usage: workspace-kit run <command> [json-args]`);
        writeLine(`Schema discovery: workspace-kit run <command> --schema-only  (JSON Schema or permissive fallback + sample args + policy metadata for every executable command).`);
        writeLine(`JSON catalog (agents): workspace-kit run --list-commands  (alias: run list-commands '{}', run --json) — stable command list + policy hints.`);
        writeLine(`Instruction files: src/modules/<module>/instructions/<command>.md — sensitive runs need JSON policyApproval (not env WORKSPACE_KIT_POLICY_APPROVAL); see ${POLICY_APPROVAL_TWO_LANES_DOC}.`);
        writeLine(`Agent CLI map: .ai/AGENT-CLI-MAP.md (navigation) + .ai/agent-cli-snippets/ (per-command schema-only JSON); maintainer depth: ${AGENT_CLI_MAP_HUMAN_DOC}.`);
        return codes.success;
    }
    try {
        const actor = await resolveActorWithFallback(cwd, commandArgs, process.env);
        const sensitive = isSensitiveModuleCommandForEffective(subcommand, commandArgs, effective);
        const sessionId = resolveSessionId(process.env);
        const policyOp = resolvePolicyOperationIdForCommand(subcommand, effective);
        const explicitPolicyApproval = parsePolicyApproval(commandArgs);
        const hasPolicyApprovalField = Object.hasOwn(commandArgs, "policyApproval");
        let resolvedSensitiveApproval = explicitPolicyApproval;
        let interactiveSessionFollowup = false;
        if (sensitive) {
            if (!resolvedSensitiveApproval && policyOp) {
                const grant = await getSessionGrant(cwd, policyOp, sessionId, effective);
                if (grant) {
                    resolvedSensitiveApproval = { confirmed: true, rationale: grant.rationale };
                }
            }
            if (!resolvedSensitiveApproval && policyOp) {
                const interactive = await promptSensitiveRunApproval({ writeError, readStdinLine: io.readStdinLine }, policyOp, `run ${subcommand}`, process.env);
                if (interactive?.kind === "deny") {
                    await appendPolicyTrace(cwd, {
                        timestamp: new Date().toISOString(),
                        operationId: policyOp,
                        command: `run ${subcommand}`,
                        actor,
                        allowed: false,
                        message: "interactive policy approval denied"
                    });
                    await emitJson(policyDeniedBody({
                        policyOp,
                        message: "Sensitive command denied at interactive policy prompt.",
                        hint: `Set WORKSPACE_KIT_INTERACTIVE_APPROVAL=off or pass policyApproval in JSON. See ${POLICY_APPROVAL_TWO_LANES_DOC}.`,
                        wrongEnvLane: false,
                        subcommand,
                        hasPolicyApprovalField
                    }));
                    return codes.validationFailure;
                }
                if (interactive?.kind === "approve") {
                    const rationale = interactive.scope === "session" ? "interactive-approval-session" : "interactive-approval-once";
                    resolvedSensitiveApproval = {
                        confirmed: true,
                        rationale,
                        ...(interactive.scope === "session" ? { scope: "session" } : {})
                    };
                    interactiveSessionFollowup = interactive.scope === "session";
                }
            }
            if (!resolvedSensitiveApproval) {
                if (policyOp) {
                    await appendPolicyTrace(cwd, {
                        timestamp: new Date().toISOString(),
                        operationId: policyOp,
                        command: `run ${subcommand}`,
                        actor,
                        allowed: false,
                        message: hasPolicyApprovalField
                            ? "invalid policyApproval in JSON args"
                            : "missing policyApproval in JSON args"
                    });
                }
                const envApprovalPresent = Boolean(parsePolicyApprovalFromEnv(process.env));
                const wrongEnvLane = envApprovalPresent && !hasPolicyApprovalField;
                const baseHint = policyOp != null
                    ? `Operation ${policyOp} requires explicit JSON policyApproval (or session grant / TTY interactive approval). Details: ${POLICY_APPROVAL_TWO_LANES_DOC}`
                    : "Check policy.extraSensitiveModuleCommands and pass policyApproval in JSON args.";
                const msg = wrongEnvLane
                    ? `Sensitive run denied: ${POLICY_RUN_ENV_LANE_MISMATCH_DETAIL} See ${POLICY_APPROVAL_TWO_LANES_DOC}.`
                    : hasPolicyApprovalField
                        ? 'Invalid policyApproval in JSON args. Use {"policyApproval":{"confirmed":true,"rationale":"why","scope":"session"}} (scope optional) or a session grant. See remediationDoc.'
                        : 'Missing policyApproval in JSON args. Example: {"policyApproval":{"confirmed":true,"rationale":"why","scope":"session"}}. See remediationDoc.';
                await emitJson(policyDeniedBody({
                    policyOp,
                    message: msg,
                    hint: wrongEnvLane ? POLICY_RUN_ENV_LANE_MISMATCH_DETAIL : baseHint,
                    wrongEnvLane,
                    subcommand,
                    hasPolicyApprovalField
                }));
                return codes.validationFailure;
            }
        }
        const ctx = {
            runtimeVersion: "0.1",
            workspacePath: cwd,
            effectiveConfig: effective,
            resolvedActor: actor,
            moduleRegistry: registry
        };
        const autoCheckpoint = await tryAutoCheckpointBeforeRun({
            workspacePath: cwd,
            effectiveConfig: effective,
            subcommand,
            actor
        });
        if (!autoCheckpoint.ok) {
            await emitJson({
                ok: false,
                code: autoCheckpoint.code,
                message: autoCheckpoint.message
            });
            return codes.validationFailure;
        }
        const hookBus = createKitLifecycleHookBus(cwd, effective);
        if (hookBus.isEnabled()) {
            const preCmd = await hookBus.emitBeforeModuleCommand(subcommand, commandArgs);
            if (preCmd.denied && hookBus.getMode() === "enforce") {
                await hookBus.emitAfterModuleCommand(subcommand, false, "hook-denied");
                await emitJson({
                    ok: false,
                    code: "hook-denied",
                    message: preCmd.denied.reason
                });
                return codes.validationFailure;
            }
            if (preCmd.commandArgsPatch && hookBus.getMode() === "enforce") {
                Object.assign(commandArgs, preCmd.commandArgsPatch);
            }
        }
        const caePre = runCaeCliPreflight({
            workspacePath: cwd,
            effective,
            subcommand,
            commandArgs,
            router
        });
        if (caePre.traceToStore) {
            storeCaeSession(caePre.traceToStore.traceId, {
                bundle: caePre.traceToStore.bundle,
                trace: caePre.traceToStore.trace
            });
            const persistShadowPreflight = getAtPath(effective, "kit.cae.persistence") === true &&
                getAtPath(effective, "kit.cae.runtime.persistShadowPreflight") === true;
            persistCaeTraceIfEnabled(cwd, effective, persistShadowPreflight, caePre.traceToStore.traceId, caePre.traceToStore.trace, caePre.traceToStore.bundle);
        }
        if (caePre.enforcementDenial) {
            const denied = applyResponseTemplateApplication(subcommand, commandArgs, caePre.enforcementDenial, effective);
            await emitJson(denied);
            return codes.validationFailure;
        }
        try {
            const rawResult = await router.execute(subcommand, commandArgs, ctx);
            if (sensitive && resolvedSensitiveApproval && policyOp) {
                const rehearsal = subcommand === "generate-recommendations" && commandArgs.dryRun === true ? "policy-rehearsal" : null;
                const traceMessage = rehearsal && rawResult.message
                    ? `${rehearsal} ${rawResult.message}`
                    : rehearsal
                        ? rehearsal
                        : rawResult.message;
                await appendPolicyTrace(cwd, {
                    timestamp: new Date().toISOString(),
                    operationId: policyOp,
                    command: `run ${subcommand}`,
                    actor,
                    allowed: true,
                    rationale: resolvedSensitiveApproval.rationale,
                    commandOk: rawResult.ok,
                    message: traceMessage
                });
                const recordSession = rawResult.ok &&
                    (explicitPolicyApproval?.scope === "session" || interactiveSessionFollowup);
                if (recordSession) {
                    await recordSessionGrant(cwd, policyOp, sessionId, resolvedSensitiveApproval.rationale, effective);
                }
            }
            const withCae = mergeCaeIntoCommandResult(rawResult, caePre.shadowAttach);
            const result = applyResponseTemplateApplication(subcommand, commandArgs, withCae, effective);
            if (hookBus.isEnabled()) {
                await hookBus.emitAfterModuleCommand(subcommand, rawResult.ok, rawResult.code);
            }
            await emitJson(result);
            return result.ok ? codes.success : codes.validationFailure;
        }
        catch (error) {
            if (error instanceof TaskEngineError) {
                const body = {
                    ok: false,
                    code: error.code,
                    message: error.message
                };
                if (error.details && Object.keys(error.details).length > 0) {
                    body.data = error.details;
                }
                if (error.code === "planning-generation-mismatch" || error.code === "planning-generation-required") {
                    body.remediation = {
                        docPath: CLI_REMEDIATION_DOCS.planningGeneration,
                        instructionPath: "src/modules/task-engine/instructions/run-transition.md"
                    };
                }
                if (hookBus.isEnabled()) {
                    await hookBus.emitAfterModuleCommand(subcommand, false, error.code);
                }
                await emitJson(body);
                return codes.validationFailure;
            }
            if (hookBus.isEnabled()) {
                const code = error instanceof ModuleCommandRouterError
                    ? error.code
                    : error instanceof Error
                        ? error.name
                        : "internal-error";
                await hookBus.emitAfterModuleCommand(subcommand, false, code);
            }
            if (error instanceof ModuleCommandRouterError) {
                await emitJson({
                    ok: false,
                    code: error.code,
                    message: error.message,
                    remediation: error.code === "unknown-command"
                        ? {
                            docPath: CLI_REMEDIATION_DOCS.agentCliMap,
                            docAnchors: [
                                "doctor --agent-instruction-surface",
                                "workspace-kit run --list-commands"
                            ]
                        }
                        : { docPath: CLI_REMEDIATION_DOCS.agentCliMap },
                    discovery: cliDiscoveryEnvelope()
                });
                return codes.validationFailure;
            }
            const message = error instanceof Error ? error.message : String(error);
            writeError(`Module command failed: ${message}`);
            return codes.internalError;
        }
    }
    finally {
        releaseTranscriptHookLockFromEnv();
    }
}
