// forge config — routing config inspector (Plan 16, Slice 3).
//
// Subcommands:
//   forge config show                  Print persona-models and pipeline overrides
//   forge config show --resolved       Same, with per-entry cascade source annotation
//   forge config show --json           Machine-readable JSON output
//   forge config show --resolved --json Both
//   forge config dispatch              Per-phase dispatch trace (no LLM call)
//   forge config dispatch --json       Machine-readable JSON output
//
// This command reads from:
//   ~/.pi/forge-cli/config.json              (L1 global — post-v0.10.0 layout)
//   <cwd>/.pi/forge-cli/config.json          (L2/L3/L4 project)
//   <cwd>/.forge/config.json                 (pipeline catalogue — optional)
// All paths resolve through `../extensions/forgecli/paths/paths.ts`.
// And checks availability against the pi ModelRegistry (same as `forge doctor`).
//
// The persona catalogue is read from the bundled base-pack personas dir.
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
import { loadLayeredConfig } from "../extensions/forgecli/config/config-layer.js";
import { BUG_PHASES } from "../extensions/forgecli/orchestrators/fix-bug.js";
import { lookupPersonaModel, resolveModelForPhase } from "../extensions/forgecli/config/model-resolver.js";
import { ensureForgeCliPathsReady, getGlobalConfigPath, getProjectConfigPath, shortenPath, } from "../extensions/forgecli/paths/paths.js";
import { PHASES } from "../extensions/forgecli/orchestrators/run-task.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
 * Parse forge config subcommand arguments.
 *
 * @implements {import("./shared-parser.js").SubcommandArgsParser<ConfigArgs>}
 */
export function parseConfigArgs(args) {
    if (args.length === 0) {
        return { subcommand: "usage", resolved: false, json: false };
    }
    const [sub, ...rest] = args;
    if (sub === "show") {
        let resolved = false;
        let json = false;
        for (const flag of rest) {
            if (flag === "--resolved") {
                resolved = true;
                continue;
            }
            if (flag === "--json") {
                json = true;
                continue;
            }
            return { error: `forge config show: unknown option ${flag}` };
        }
        return { subcommand: "show", resolved, json };
    }
    if (sub === "dispatch") {
        let json = false;
        let pipeline;
        for (const flag of rest) {
            if (flag === "--json") {
                json = true;
                continue;
            }
            if (flag.startsWith("--pipeline=")) {
                pipeline = flag.slice("--pipeline=".length);
                continue;
            }
            return { error: `forge config dispatch: unknown option ${flag}` };
        }
        return { subcommand: "dispatch", resolved: false, json, pipeline };
    }
    return {
        error: `forge config: unknown subcommand "${sub}". Try: forge config show [--resolved] [--json] | dispatch [--pipeline=<name>] [--json]`,
    };
}
// ── Persona catalogue ────────────────────────────────────────────────────────
function readPersonaCatalogue() {
    const personasDir = path.resolve(__dirname, "..", "forge-payload", ".base-pack", "personas");
    try {
        return fs
            .readdirSync(personasDir)
            .filter((f) => f.endsWith(".md") && !f.endsWith("-skills.md"))
            .map((f) => f.replace(/\.md$/, ""));
    }
    catch {
        return [];
    }
}
// ── Pipeline catalogue from .forge/config.json ───────────────────────────────
function readPipelineCatalogue(cwd) {
    const forgeCfgPath = path.join(cwd, ".forge", "config.json");
    try {
        const raw = fs.readFileSync(forgeCfgPath, "utf-8");
        const cfg = JSON.parse(raw);
        if (cfg && typeof cfg === "object" && "pipelines" in cfg) {
            const pipelines = cfg.pipelines;
            if (pipelines && typeof pipelines === "object") {
                return Object.keys(pipelines);
            }
            return [];
        }
        return null;
    }
    catch {
        return null;
    }
}
export async function runConfigShow(opts, cwd, write) {
    const { resolved: showResolved, json } = opts;
    // N-B-E: surface schema errors informally; exit 0 (CLI inspector stays useful even with a broken config).
    // See doc/decisions/layered-config-error-policy.md.
    const layeredShow = loadLayeredConfig(cwd);
    if (layeredShow.errors.length > 0) {
        for (const e of layeredShow.errors) {
            write(`⚠ forge config: schema error — ${e}`);
        }
    }
    const { merged } = layeredShow;
    const personaCatalogue = readPersonaCatalogue();
    const pipelineCatalogue = readPipelineCatalogue(cwd);
    const authStorage = AuthStorage.create();
    const modelRegistry = ModelRegistry.create(authStorage);
    const available = modelRegistry.getAvailable();
    const hasPersonaModels = merged._global?.["persona-models"] !== undefined || merged._project?.["persona-models"] !== undefined;
    const hasPipelines = merged.pipelines !== undefined && Object.keys(merged.pipelines).length > 0;
    if (!hasPersonaModels && !hasPipelines) {
        if (json) {
            write(JSON.stringify({ personaModels: {}, pipelines: {} }, null, 2));
        }
        else {
            write("No forge-cli model routing config found.");
            write("");
            write(`Create ${shortenPath(getGlobalConfigPath())} or ${getProjectConfigPath(cwd)}`);
            write("to configure persona → model routing.");
        }
        return 0;
    }
    // Build the persona entries map
    const personaModels = {};
    // Collect all persona names defined across both layers
    const allPersonaNames = new Set([
        ...Object.keys(merged._global?.["persona-models"] ?? {}),
        ...Object.keys(merged._project?.["persona-models"] ?? {}),
    ]);
    for (const personaName of Array.from(allPersonaNames).sort()) {
        const lookup = lookupPersonaModel(personaName, "default", merged);
        if (!lookup.model)
            continue;
        const isAvail = available.some((m) => m.provider === lookup.model.provider && m.id === lookup.model.model);
        personaModels[personaName] = {
            persona: personaName,
            provider: lookup.model.provider,
            model: lookup.model.model,
            available: isAvail,
            source: lookup.source,
        };
    }
    // Build pipeline output
    const pipelinesOutput = {};
    for (const [pipelineName, pipelineCfg] of Object.entries(merged.pipelines ?? {})) {
        const pipelinePersonaModels = {};
        for (const [key, value] of Object.entries(pipelineCfg["persona-models"] ?? {})) {
            const isAvail = available.some((m) => m.provider === value.provider && m.id === value.model);
            pipelinePersonaModels[key] = {
                persona: key,
                provider: value.provider,
                model: value.model,
                available: isAvail,
                source: "L3",
            };
        }
        const phasesOutput = Object.entries(pipelineCfg.phases ?? {}).map(([role, phase]) => {
            const override = phase["model-override"];
            if (!override)
                return { role, override: null, available: true, source: "inherit" };
            if (typeof override === "string") {
                return { role, override, available: true, source: "L4-name" };
            }
            const isAvail = available.some((m) => m.provider === override.provider && m.id === override.model);
            return {
                role,
                override: `${override.provider}:${override.model}`,
                available: isAvail,
                source: "L4-inline",
            };
        });
        pipelinesOutput[pipelineName] = { personaModels: pipelinePersonaModels, phases: phasesOutput };
    }
    if (json) {
        write(JSON.stringify({ personaModels, pipelines: pipelinesOutput }, null, 2));
        return 0;
    }
    // Human-readable output
    write("forge config — model routing" + (showResolved ? " (resolved)" : ""));
    write("");
    if (Object.keys(personaModels).length > 0) {
        write("persona-models:");
        const colWidths = {
            persona: Math.max(8, ...Object.keys(personaModels).map((k) => k.length)),
            model: Math.max(5, ...Object.values(personaModels).map((e) => `${e.provider}:${e.model}`.length)),
        };
        for (const entry of Object.values(personaModels)) {
            const availBadge = entry.available ? "✓" : "✗ unavailable";
            const modelStr = `${entry.provider}:${entry.model}`;
            const personaPad = entry.persona.padEnd(colWidths.persona);
            const modelPad = modelStr.padEnd(colWidths.model);
            if (showResolved) {
                write(`  ${personaPad}  →  ${modelPad}  ${availBadge}  ${entry.source}`);
            }
            else {
                write(`  ${personaPad}  →  ${modelPad}  ${availBadge}`);
            }
        }
        write("");
    }
    if (pipelineCatalogue !== null && hasPipelines) {
        write("pipeline overrides:");
        for (const [pName, pOut] of Object.entries(pipelinesOutput)) {
            const inCatalogue = pipelineCatalogue.includes(pName);
            const marker = inCatalogue ? "" : " ⚠ (not in .forge/config.json)";
            write(`  [${pName}]${marker}`);
            for (const [key, entry] of Object.entries(pOut.personaModels)) {
                const badge = entry.available ? "✓" : "✗ unavailable";
                write(`    ${key}  →  ${entry.provider}:${entry.model}  ${badge}`);
            }
            for (const phase of pOut.phases) {
                if (phase.override) {
                    const badge = phase.available ? "✓" : "✗ unavailable";
                    write(`    phase[${phase.role}]  →  ${phase.override}  ${badge}  ${phase.source}`);
                }
            }
        }
        write("");
    }
    return 0;
}
/**
 * Walk run-task's PHASES and fix-bug's BUG_PHASES; for each, compute the requested
 * model via the same resolveModelForPhase the orchestrator uses, and verify that
 * pi's ModelRegistry can resolve the requested id. Prints a table or JSON.
 *
 * No LLM call. No pi session. Pure inspection.
 */
export function runConfigDispatch(args, cwd, write) {
    const taskPipeline = args.pipeline ?? "default";
    const bugPipeline = "fix-bug";
    // N-B-E: surface schema errors informally; exit 0 (CLI inspector stays useful even with a broken config).
    // See doc/decisions/layered-config-error-policy.md.
    const layeredDispatch = loadLayeredConfig(cwd);
    if (layeredDispatch.errors.length > 0) {
        for (const e of layeredDispatch.errors) {
            write(`⚠ forge config: schema error — ${e}`);
        }
    }
    const { merged } = layeredDispatch;
    const registry = ModelRegistry.create(AuthStorage.create());
    const traceOne = (pipelineName, phaseRole, personaNoun) => {
        const res = resolveModelForPhase(pipelineName, phaseRole, personaNoun, merged);
        if (!res.model) {
            return {
                pipeline: pipelineName,
                phaseRole,
                persona: personaNoun,
                requested: null,
                source: res.source,
                registered: null,
                registryId: null,
            };
        }
        const found = registry.find?.(res.model.provider, res.model.model);
        return {
            pipeline: pipelineName,
            phaseRole,
            persona: personaNoun,
            requested: { provider: res.model.provider, model: res.model.model },
            source: res.source,
            registered: !!found,
            registryId: found?.id ?? null,
        };
    };
    const taskPhases = PHASES.map((p) => traceOne(taskPipeline, p.role, p.personaNoun));
    const bugPhases = BUG_PHASES.map((p) => traceOne(bugPipeline, p.role, p.personaNoun));
    if (args.json) {
        const out = { cwd, taskPipeline, taskPhases, bugPhases };
        write(JSON.stringify(out, null, 2));
        return 0;
    }
    // Human-readable table.
    const fmt = (row) => {
        const req = row.requested ? `${row.requested.provider}:${row.requested.model}` : "(inherit pi current)";
        let find;
        if (row.registered === null) {
            find = "(skipped — setModel not called)";
        }
        else if (row.registered) {
            find = `✓ ${row.registryId}`;
        }
        else {
            find = `✗ MISS — setModel will silently inherit pi current`;
        }
        return `${row.phaseRole.padEnd(13)} ${row.persona.padEnd(16)} ${req.padEnd(36)} ${find}`;
    };
    write(`forge config dispatch — cwd=${cwd}`);
    write("");
    write("Note: this trace uses the baseline ModelRegistry built from AuthStorage");
    write("only — it does NOT see providers that get registered by extensions at");
    write("session start (ollama-cloud, openrouter, …). Inside a live forge session");
    write("runForgeSubagent now uses ctx.modelRegistry, which does see them. A MISS");
    write("here means the model is extension-registered, not that dispatch will fail.");
    write("");
    write(`── run-task PHASES (pipeline: ${taskPipeline}) ─────────────────────────────`);
    write("phase         persona          requested                            registry.find");
    write("─────         ───────          ─────────                            ─────────────");
    for (const row of taskPhases)
        write(fmt(row));
    write("");
    write(`── fix-bug BUG_PHASES (pipeline: ${bugPipeline}) ───────────────────────────`);
    write("phase         persona          requested                            registry.find");
    write("─────         ───────          ─────────                            ─────────────");
    for (const row of bugPhases)
        write(fmt(row));
    write("");
    write("source map (raw resolver):");
    for (const row of taskPhases) {
        write(`  task[${row.phaseRole.padEnd(13)}] ${row.source}`);
    }
    for (const row of bugPhases) {
        write(`  bug [${row.phaseRole.padEnd(13)}] ${row.source}`);
    }
    return 0;
}
// ── Entry point ───────────────────────────────────────────────────────────────
export async function runConfig(args, cwd = process.cwd()) {
    // Trigger one-shot user-data layout migration so subsequent path reads
    // resolve to the post-v0.10.0 layout (FORGE-S20-T11).
    ensureForgeCliPathsReady();
    // Plan 16 Slice 4b: delegate to runConfigTui so the bin and pi-extension
    // surfaces share the same argv parser + routing. The bin has no ctx, so
    // interactive subcommands print a "use /forge:config from pi" fallback.
    // Lazy import to avoid a circular dependency at module load.
    const { runConfigTui } = await import("../extensions/forgecli/config-tui/handler.js");
    return runConfigTui(args, cwd, {
        write: (s) => process.stdout.write(s),
        writeErr: (s) => process.stderr.write(s),
    });
}
//# sourceMappingURL=config.js.map