"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputBudgetError = exports.APPROVED_BOUNDED_READ_COMMANDS = exports.JSON_SPACING_VALUES = exports.DEFAULT_COMPACT_OUTPUT_BUDGET_BYTES = exports.MINIMUM_OUTPUT_BUDGET_BYTES = void 0;
exports.isApprovedBoundedReadCommand = isApprovedBoundedReadCommand;
exports.serializeBudgetedEnvelope = serializeBudgetedEnvelope;
exports.MINIMUM_OUTPUT_BUDGET_BYTES = 2048;
exports.DEFAULT_COMPACT_OUTPUT_BUDGET_BYTES = 8192;
exports.JSON_SPACING_VALUES = ["compact", "pretty"];
exports.APPROVED_BOUNDED_READ_COMMANDS = [
    "about",
    "agent",
    "agent context",
    "agent remind",
    "commands inventory",
    "commands manifest",
    "commands search",
    "config inventory",
    "doctor",
    "metadata inventory",
    "planning gap-report",
    "planning inventory",
    "planning next-action",
    "resume inspect",
    "resume resume",
    "spec list",
    "spec search",
    "structural-pressure report",
    "templates list",
    "validation links-check",
    "validation rollup",
    "validation smoke",
    "version doctor",
    "work candidates",
    "work current",
    "work next",
    "workflow explain"
];
class OutputBudgetError extends Error {
    code;
    minimumBytes;
    constructor(code, message, minimumBytes) {
        super(message);
        this.name = "OutputBudgetError";
        this.code = code;
        this.minimumBytes = minimumBytes;
    }
}
exports.OutputBudgetError = OutputBudgetError;
function isApprovedBoundedReadCommand(command) {
    return exports.APPROVED_BOUNDED_READ_COMMANDS.includes(command);
}
function serializeBudgetedEnvelope(source, options) {
    validateBudget(options.byteBudget, options.spacing);
    const envelope = cloneEnvelope(source);
    setBudgetMetadata(envelope, {
        byteBudget: options.byteBudget,
        renderedBytes: 1,
        serialization: options.spacing,
        strategy: "within-budget",
        recovery: []
    });
    const withinBudget = stabilizeEnvelope(envelope, options.spacing);
    if (withinBudget.bytes <= options.byteBudget) {
        return withinBudget;
    }
    const bounded = createBoundedSummary(envelope, options.byteBudget);
    const boundedResult = stabilizeEnvelope(bounded, "compact");
    if (boundedResult.bytes <= options.byteBudget) {
        return boundedResult;
    }
    const minimal = createMinimalFallback(bounded, options.byteBudget);
    const minimalResult = stabilizeEnvelope(minimal, "compact");
    if (minimalResult.bytes <= options.byteBudget) {
        return minimalResult;
    }
    throw new OutputBudgetError("OUTPUT_BUDGET_IRREDUCIBLE", `The smallest safe protocol envelope requires ${minimalResult.bytes} bytes.`, minimalResult.bytes);
}
function createBoundedSummary(source, byteBudget) {
    const envelope = cloneEnvelope(source);
    const findings = getFindings(envelope);
    const blockers = compactBlockerRepresentatives(findings);
    const terminalAgentBootstrap = envelope.status === "ok"
        && envelope.command === "agent"
        && findings.length === 0;
    const recovery = terminalAgentBootstrap
        ? []
        : [
            "Rerun with --fields, --limit, or --page-lane/--page-size to request bounded detail.",
            "Increase --output-budget or use --detail full without a budget for audit output."
        ];
    setFindings(envelope, blockers);
    envelope.output.complete = terminalAgentBootstrap;
    envelope.output.truncated = !terminalAgentBootstrap;
    envelope.output.findingGroups = groupBlockers(findings);
    envelope.output.lanes = {
        findings: {
            originalCount: findings.length,
            returnedCount: blockers.length,
            omittedCount: Math.max(0, findings.length - blockers.length),
            complete: blockers.length === findings.length
        }
    };
    delete envelope.output.page;
    setBudgetMetadata(envelope, {
        byteBudget,
        renderedBytes: 1,
        serialization: "compact",
        strategy: "bounded-summary",
        recovery
    });
    if (envelope.status === "ok") {
        envelope.data = terminalAgentBootstrap
            ? summarizeAgentOrientationData(envelope.data)
            : summarizeData(envelope.data, recovery);
    }
    else {
        envelope.error.message = truncateText(envelope.error.message, 256);
        envelope.error.details = envelope.error.details.slice(0, 3).map((detail) => truncateText(detail, 256));
        if (envelope.data !== undefined) {
            envelope.data = summarizeData(envelope.data, recovery);
        }
    }
    return envelope;
}
function summarizeAgentOrientationData(data) {
    const source = isRecord(data) ? data : {};
    const rootContext = isRecord(source.rootContext) ? source.rootContext : {};
    const entrypoint = isRecord(rootContext.agentEntrypoint) ? rootContext.agentEntrypoint : {};
    return {
        compactOrientation: true,
        lane: "bootstrap",
        ...(isRecord(source.runtimeProvenance) ? {
            runtimeProvenance: source.runtimeProvenance
        } : {}),
        workspace: {
            effectiveRoot: rootContext.effectiveRoot ?? ".",
            detectionSource: rootContext.detectionSourceLabel ?? null,
            markerPath: rootContext.markerPath ?? null,
            agentEntrypoint: entrypoint.path ?? ".day-shift/agents.md"
        },
        complete: true,
        scopedBootstrap: [
            "day-shift work candidates --format json --detail compact",
            "day-shift agent context --artifact <path> --format json --detail compact",
            "day-shift doctor --artifact <path> --format json"
        ],
        routes: {
            noArtifact: [
                "day-shift work candidates --format json --detail compact"
            ],
            explicitArtifact: [
                "day-shift agent context --artifact <path> --format json --detail compact",
                "day-shift doctor --artifact <path> --format json"
            ]
        },
        recovery: [
            "Follow only typed recovery returned by the selected command when decision-relevant evidence is incomplete."
        ],
        audit: {
            command: "day-shift agent orient --format json",
            automatic: false,
            useWhen: "Run only when the caller explicitly requests full repository-orientation audit evidence."
        }
    };
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function createMinimalFallback(source, byteBudget) {
    const envelope = cloneEnvelope(source);
    const findings = getFindings(envelope);
    const recovery = ["Use pagination, selected fields, or a larger output budget."];
    envelope.output.complete = false;
    envelope.output.truncated = true;
    envelope.output.findingGroups = groupBlockers(findings);
    envelope.output.lanes = {
        findings: {
            originalCount: findings.length,
            returnedCount: 0,
            omittedCount: findings.length,
            complete: findings.length === 0
        }
    };
    delete envelope.output.page;
    setFindings(envelope, []);
    setBudgetMetadata(envelope, {
        byteBudget,
        renderedBytes: 1,
        serialization: "compact",
        strategy: "minimal-fallback",
        recovery
    });
    if (envelope.status === "ok") {
        envelope.data = { bounded: true, recovery };
    }
    else {
        envelope.error.message = "Output was reduced to fit the selected byte budget.";
        envelope.error.details = recovery;
        delete envelope.data;
    }
    return envelope;
}
function stabilizeEnvelope(envelope, spacing) {
    for (let attempt = 0; attempt < 8; attempt += 1) {
        const text = `${JSON.stringify(envelope, null, spacing === "pretty" ? 2 : undefined)}\n`;
        const bytes = Buffer.byteLength(text, "utf8");
        const budget = envelope.output.budget;
        if (budget?.renderedBytes === bytes) {
            return { envelope, text, bytes };
        }
        if (budget === undefined) {
            throw new TypeError("Budgeted envelope metadata is missing.");
        }
        budget.renderedBytes = bytes;
    }
    throw new TypeError("Budgeted envelope byte metadata did not stabilize.");
}
function summarizeData(data, recovery) {
    if (typeof data === "object" && data !== null && !Array.isArray(data)) {
        const fields = Object.keys(data);
        return {
            bounded: true,
            originalTopLevelFieldCount: fields.length,
            omittedTopLevelFields: fields,
            ...(isRecord(data.runtimeProvenance) ? {
                runtimeProvenance: data.runtimeProvenance
            } : {}),
            recovery
        };
    }
    return {
        bounded: true,
        originalKind: Array.isArray(data) ? "array" : typeof data,
        recovery
    };
}
function compactBlockerRepresentatives(findings) {
    const seen = new Set();
    const blockers = [];
    for (const finding of findings) {
        if (finding?.severity !== "error" || typeof finding.code !== "string") {
            continue;
        }
        const key = `${finding.code}\u0000${finding.severity}`;
        if (!seen.has(key)) {
            seen.add(key);
            blockers.push({
                ...(typeof finding.id === "string" ? { id: finding.id } : {}),
                code: finding.code,
                severity: "error",
                message: truncateText(String(finding.message ?? finding.code), 160)
            });
        }
    }
    return blockers;
}
function groupBlockers(findings) {
    const counts = new Map();
    for (const finding of findings) {
        if (finding?.severity === "error" && typeof finding.code === "string") {
            counts.set(finding.code, (counts.get(finding.code) ?? 0) + 1);
        }
    }
    return [...counts].map(([code, count]) => ({ code, severity: "error", count }));
}
function getFindings(envelope) {
    return envelope.status === "ok" ? envelope.findings : envelope.error.findings;
}
function setFindings(envelope, findings) {
    if (envelope.status === "ok") {
        envelope.findings = findings;
    }
    else {
        envelope.error.findings = findings;
    }
}
function setBudgetMetadata(envelope, budget) {
    envelope.output.budget = budget;
}
function cloneEnvelope(source) {
    return JSON.parse(JSON.stringify(source));
}
function truncateText(value, maximum) {
    return value.length <= maximum ? value : `${value.slice(0, maximum - 1)}…`;
}
function validateBudget(byteBudget, spacing) {
    if (!Number.isSafeInteger(byteBudget) || byteBudget < exports.MINIMUM_OUTPUT_BUDGET_BYTES) {
        throw new OutputBudgetError("OUTPUT_BUDGET_INVALID", `Output budget must be a safe integer of at least ${exports.MINIMUM_OUTPUT_BUDGET_BYTES} bytes.`, exports.MINIMUM_OUTPUT_BUDGET_BYTES);
    }
    if (!exports.JSON_SPACING_VALUES.includes(spacing)) {
        throw new OutputBudgetError("OUTPUT_BUDGET_INVALID", `JSON spacing must be one of: ${exports.JSON_SPACING_VALUES.join(", ")}.`, exports.MINIMUM_OUTPUT_BUDGET_BYTES);
    }
}
