import AjvImport from "ajv";
import { existsSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { cliDiscoveryEnvelope } from "./cli-discovery.js";
import { CLI_REMEDIATION_DOCS, CLI_REMEDIATION_INSTRUCTIONS } from "./cli-remediation.js";
import { getBuiltinRunCommandManifestRow } from "../contracts/builtin-run-command-manifest.js";
/** Stripped before AJV: global response-template shaping, not domain args (see response-template-shaping). */
const RESPONSE_TEMPLATE_RUN_OVERLAY_KEYS = [
    "responseTemplateId",
    "responseTemplateDirective",
    "instructionTemplateDirective",
    "instruction"
];
function pilotArgsForSchemaValidation(args) {
    const out = { ...args };
    for (const k of RESPONSE_TEMPLATE_RUN_OVERLAY_KEYS) {
        delete out[k];
    }
    return out;
}
function resolvePackageRoot() {
    return join(dirname(fileURLToPath(import.meta.url)), "../..");
}
function loadPilotSnapshot() {
    const root = resolvePackageRoot();
    const snapPath = join(root, "schemas", "pilot-run-args.snapshot.json");
    if (!existsSync(snapPath)) {
        throw new Error(`Missing pilot args snapshot at ${snapPath}`);
    }
    const raw = JSON.parse(readFileSync(snapPath, "utf8"));
    if (raw.schemaVersion !== 1 || !Array.isArray(raw.pilotCommands) || !raw.commands) {
        throw new Error("Invalid pilot-run-args.snapshot.json structure");
    }
    return raw;
}
function loadPlanningPreludeCommands() {
    const root = resolvePackageRoot();
    const p = join(root, "schemas", "planning-generation-cli-prelude.json");
    if (!existsSync(p)) {
        return new Set();
    }
    const raw = JSON.parse(readFileSync(p, "utf8"));
    if (!Array.isArray(raw.commands)) {
        return new Set();
    }
    return new Set(raw.commands);
}
let compiled = null;
let preludeSet = null;
function createPilotAjv() {
    const Ctor = AjvImport;
    return new Ctor({ allErrors: true, strict: false, allowUnionTypes: true });
}
function getCompiledValidators() {
    if (compiled) {
        return compiled;
    }
    const snap = loadPilotSnapshot();
    const ajv = createPilotAjv();
    const m = new Map();
    for (const cmd of snap.pilotCommands) {
        const schema = snap.commands[cmd];
        if (!schema) {
            throw new Error(`Pilot snapshot missing commands.${cmd}`);
        }
        m.set(cmd, ajv.compile(schema));
    }
    compiled = m;
    return m;
}
/** Reset cached validators (tests). */
export function resetPilotRunArgsValidationCache() {
    compiled = null;
    preludeSet = null;
}
function getPreludeCommandSet() {
    if (preludeSet) {
        return preludeSet;
    }
    preludeSet = loadPlanningPreludeCommands();
    return preludeSet;
}
function readPlanningGenerationPolicy(effective) {
    const tasks = effective.tasks;
    if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) {
        return "off";
    }
    const raw = tasks.planningGenerationPolicy;
    if (raw === "warn" || raw === "require") {
        return raw;
    }
    return "off";
}
function readOptionalExpectedPlanningGeneration(args) {
    const v = args.expectedPlanningGeneration;
    if (typeof v === "number" && Number.isInteger(v) && v >= 0) {
        return v;
    }
    if (typeof v === "string" && /^\d+$/.test(v.trim())) {
        return Number(v.trim());
    }
    return undefined;
}
function instructionPathForCommand(commandName) {
    switch (commandName) {
        case "run-transition":
            return CLI_REMEDIATION_INSTRUCTIONS.runTransition;
        case "synthesize-transcript-churn":
            return CLI_REMEDIATION_INSTRUCTIONS.synthesizeTranscriptChurn;
        case "create-task":
        case "create-task-from-plan":
        case "convert-phase-note-to-task":
            return CLI_REMEDIATION_INSTRUCTIONS.createTask;
        case "update-task":
            return CLI_REMEDIATION_INSTRUCTIONS.updateTask;
        case "get-last-output":
            return CLI_REMEDIATION_INSTRUCTIONS.getLastOutput;
        case "dashboard-summary":
            return CLI_REMEDIATION_INSTRUCTIONS.dashboardSummary;
        case "create-wishlist":
            return CLI_REMEDIATION_INSTRUCTIONS.createWishlist;
        case "archive-task":
            return CLI_REMEDIATION_INSTRUCTIONS.archiveTask;
        case "add-dependency":
        case "remove-dependency":
            return CLI_REMEDIATION_INSTRUCTIONS.addDependency;
        case "generate-recommendations":
        case "ingest-transcripts":
            return CLI_REMEDIATION_INSTRUCTIONS.generateRecommendations;
        default:
            return `src/modules/task-engine/instructions/${commandName}.md`;
    }
}
const UPDATE_TASK_MUTABLE_ROOT_KEYS = new Set([
    "title",
    "type",
    "priority",
    "dependsOn",
    "unblocks",
    "phase",
    "phaseKey",
    "metadata",
    "ownership",
    "approach",
    "summary",
    "description",
    "risk",
    "technicalScope",
    "acceptanceCriteria",
    "features"
]);
function updateTaskArgsShapeHint(errors, args) {
    const list = errors ?? [];
    const additionalAtRoot = list
        .filter((e) => e.keyword === "additionalProperties" && (e.instancePath ?? "") === "")
        .map((e) => {
        const params = e.params;
        return typeof params.additionalProperty === "string" ? params.additionalProperty : "";
    })
        .filter((key) => key.length > 0 && UPDATE_TASK_MUTABLE_ROOT_KEYS.has(key));
    const misplacedFromArgs = [...UPDATE_TASK_MUTABLE_ROOT_KEYS].filter((key) => args[key] !== undefined && args.updates === undefined);
    const misplaced = [...new Set([...additionalAtRoot, ...misplacedFromArgs])];
    if (misplaced.length === 0) {
        return { messageSuffix: "" };
    }
    const exampleField = misplaced[0];
    const exampleValue = exampleField === "metadata"
        ? { issue: "…", supportingReasoning: "…" }
        : exampleField === "title"
            ? "Updated title"
            : "…";
    return {
        messageSuffix: ` Put ${misplaced.join(", ")} inside "updates": { … } — shallow-merge replaces the whole object for nested maps (see update-task.md).`,
        expectedShape: {
            taskId: typeof args.taskId === "string" ? args.taskId : "T###",
            updates: { [exampleField]: exampleValue }
        }
    };
}
function formatAjvFailure(commandName, errors, args = {}) {
    const list = (errors ?? []).map((e) => ({
        instancePath: e.instancePath ?? "",
        keyword: e.keyword,
        message: e.message ?? "",
        params: e.params
    }));
    const updateTaskHint = commandName === "update-task" ? updateTaskArgsShapeHint(errors, args) : { messageSuffix: "" };
    const remediation = {
        instructionPath: instructionPathForCommand(commandName),
        docPath: CLI_REMEDIATION_DOCS.agentCliMap
    };
    if (updateTaskHint.expectedShape) {
        remediation.expectedShape = updateTaskHint.expectedShape;
    }
    return {
        ok: false,
        code: "invalid-run-args",
        message: `Invalid JSON args for workspace-kit run ${commandName}.${updateTaskHint.messageSuffix}`,
        details: {
            command: commandName,
            errors: list
        },
        remediation,
        discovery: cliDiscoveryEnvelope()
    };
}
/** Minimal sample args from JSON Schema `required` + `properties` (session bootstrap / --schema-only). */
function minimalSampleFromArgsSchema(schema) {
    const req = Array.isArray(schema.required) ? schema.required : [];
    const props = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)
        ? schema.properties
        : {};
    const out = {};
    for (const key of req) {
        const p = props[key];
        if (!p || typeof p !== "object" || Array.isArray(p)) {
            out[key] = null;
            continue;
        }
        const t = p.type;
        if (t === "string") {
            if (typeof p.pattern === "string" && p.pattern.includes("T[")) {
                out[key] = "T000";
            }
            else if (typeof p.enum === "object" && Array.isArray(p.enum) && p.enum.length > 0) {
                out[key] = p.enum[0];
            }
            else {
                out[key] = "";
            }
        }
        else if (t === "integer" || t === "number") {
            out[key] = 0;
        }
        else if (t === "boolean") {
            out[key] = false;
        }
        else if (t === "object") {
            out[key] = {};
        }
        else if (t === "array") {
            out[key] = [];
        }
        else if (Array.isArray(p.oneOf) || Array.isArray(p.anyOf)) {
            out[key] = 0;
        }
        else {
            out[key] = null;
        }
    }
    if (req.includes("policyApproval") && out.policyApproval === null) {
        out.policyApproval = { confirmed: true, rationale: "example" };
    }
    if (req.includes("updates") && out.updates === null) {
        out.updates = {};
    }
    return out;
}
function sampleArgsForCommand(commandName, schema) {
    if (commandName === "persist-planning-execution-drafts") {
        return {
            targetPhaseKey: "73",
            targetPhase: "Phase 73",
            desiredStatus: "ready",
            tasks: [
                {
                    id: "T900",
                    title: "Draft follow-up",
                    approach: "Implement the follow-up",
                    technicalScope: ["Wire the command path"],
                    acceptanceCriteria: ["Batch persists without row-level phase"]
                }
            ],
            expectedPlanningGeneration: 0
        };
    }
    return minimalSampleFromArgsSchema(schema);
}
function buildPermissiveArgsSchema(commandName) {
    return {
        type: "object",
        additionalProperties: true,
        description: `Permissive fallback args schema for '${commandName}'. Use instructionPath for command-specific field guidance until a stricter schema is added.`
    };
}
function buildRunArgsExample(commandName, sampleArgs) {
    return {
        description: "Minimal argv example",
        argv: `workspace-kit run ${commandName} '${JSON.stringify(sampleArgs)}'`
    };
}
function hasClientMutationId(schema) {
    const props = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)
        ? schema.properties
        : {};
    return Object.hasOwn(props, "clientMutationId");
}
/**
 * JSON payload for `workspace-kit run <command> --schema-only`.
 *
 * Strict schemas come from the pilot snapshot. Commands without strict validation still get
 * router/manifest-backed discovery so every executable run command has one machine-readable shape.
 */
export function buildRunArgsSchemaOnlyPayload(commandName, metadata) {
    const snap = loadPilotSnapshot();
    const strictSchema = snap.commands[commandName];
    const manifest = getBuiltinRunCommandManifestRow(commandName);
    if (!strictSchema && !metadata && !manifest) {
        return null;
    }
    const schema = strictSchema ?? buildPermissiveArgsSchema(commandName);
    const sampleArgs = sampleArgsForCommand(commandName, schema);
    const instructionPath = metadata?.instructionPath ?? (manifest ? `src/modules/${manifest.moduleId}/instructions/${manifest.file}` : instructionPathForCommand(commandName));
    const preludeCommands = getPreludeCommandSet();
    const policySensitivity = manifest?.policySensitivity ?? "non-sensitive";
    return {
        ok: true,
        code: "run-args-schema",
        command: commandName,
        schema,
        schemaSource: strictSchema ? "pilot-run-args-snapshot" : "manifest-permissive-fallback",
        sampleArgs,
        examples: [buildRunArgsExample(commandName, sampleArgs)],
        instructionPath,
        moduleId: metadata?.moduleId ?? manifest?.moduleId ?? null,
        description: metadata?.description ?? manifest?.description ?? null,
        policy: {
            sensitivity: policySensitivity,
            operationId: manifest?.policyOperationId ?? null,
            jsonApprovalRequired: policySensitivity === "sensitive"
                ? true
                : policySensitivity === "sensitive-with-dryrun"
                    ? "when dryRun is false or omitted by command policy"
                    : false
        },
        planningGeneration: {
            cliPrelude: preludeCommands.has(commandName),
            expectedPlanningGeneration: preludeCommands.has(commandName)
                ? "required when tasks.planningGenerationPolicy is require"
                : "not required by CLI prelude"
        },
        idempotency: {
            clientMutationId: hasClientMutationId(schema)
        },
        responseTemplate: {
            defaultResponseTemplateId: manifest?.defaultResponseTemplateId ?? null
        },
        remediationContract: CLI_REMEDIATION_DOCS.remediationContract
    };
}
/**
 * When the command is in the pilot snapshot, validates CLI JSON args before module dispatch.
 * @returns Structured error payload for JSON stdout, or null when validation passes or command is not in the pilot.
 */
export function validatePilotRunCommandArgs(commandName, args, _effectiveConfig) {
    const snap = loadPilotSnapshot();
    if (!snap.pilotCommands.includes(commandName)) {
        return null;
    }
    const validators = getCompiledValidators();
    const validate = validators.get(commandName);
    if (!validate) {
        return null;
    }
    const domainArgs = pilotArgsForSchemaValidation(args);
    if (!validate(domainArgs)) {
        return formatAjvFailure(commandName, validate.errors, domainArgs);
    }
    return null;
}
/**
 * Early planning-generation-required for selected mutators (before policy/session work).
 * `generate-recommendations` dryRun skips the gate (no task writes).
 */
export function enforcePlanningGenerationCliPrelude(commandName, args, effectiveConfig) {
    const policy = readPlanningGenerationPolicy(effectiveConfig);
    if (policy !== "require") {
        return null;
    }
    if (!getPreludeCommandSet().has(commandName)) {
        return null;
    }
    if (commandName === "generate-recommendations" && args.dryRun === true) {
        return null;
    }
    if (readOptionalExpectedPlanningGeneration(args) !== undefined) {
        return null;
    }
    return {
        ok: false,
        code: "planning-generation-required",
        message: "tasks.planningGenerationPolicy is 'require': include expectedPlanningGeneration from a prior read (planningGeneration on responses); retry after re-read when you get planning-generation-mismatch",
        remediation: {
            instructionPath: instructionPathForCommand(commandName),
            docPath: CLI_REMEDIATION_DOCS.planningGeneration
        }
    };
}
