"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldRepairWorkerDecision = shouldRepairWorkerDecision;
exports.buildRepairContext = buildRepairContext;
exports.buildStructuredDraftRepairContext = buildStructuredDraftRepairContext;
exports.extractDecisionBlock = extractDecisionBlock;
exports.emitRepairRetryStartDiagnostic = emitRepairRetryStartDiagnostic;
exports.emitRepairRetryEndDiagnostic = emitRepairRetryEndDiagnostic;
exports.emitStructuredDraftRepairRetryStartDiagnostic = emitStructuredDraftRepairRetryStartDiagnostic;
exports.emitStructuredDraftRepairRetryEndDiagnostic = emitStructuredDraftRepairRetryEndDiagnostic;
exports.validateDecision = validateDecision;
exports.wrapWorkerRunError = wrapWorkerRunError;
exports.classifyWorkerOutputFailure = classifyWorkerOutputFailure;
exports.buildWorkerEnvironment = buildWorkerEnvironment;
const store_1 = require("../auth-profiles/store");
const environment_1 = require("../auth-profiles/environment");
const branding_1 = require("../core/branding");
const command_1 = require("../core/command");
const catalog_1 = require("../i18n/catalog");
const state_1 = require("../workflows/state");
const decision_output_1 = require("./decision-output");
const errors_1 = require("./errors");
class WorkerDecisionSemanticError extends errors_1.WorkerDecisionParseError {
    previousDecisionBlock;
    constructor(message, input) {
        super(message, {
            attempted: true,
            cause: input.cause,
            decisionBlock: input.previousDecisionBlock,
            issues: [...input.issues],
            validationIssues: [...input.issues],
            ...(input.repairEligible !== undefined
                ? { repairEligible: input.repairEligible }
                : {}),
        });
        this.name = "WorkerDecisionSemanticError";
        this.previousDecisionBlock = input.previousDecisionBlock;
    }
}
const CLI_LOG_PREFIX = `[${branding_1.CLI_COMMAND_NAME}]`;
function shouldRepairWorkerDecision(error) {
    return ((error instanceof errors_1.WorkerDecisionParseError ||
        error instanceof WorkerDecisionSemanticError) &&
        error.repairEligible);
}
function buildRepairContext(rawOutput, error) {
    if (error instanceof WorkerDecisionSemanticError) {
        return {
            kind: "semantic_invalid",
            previousDecisionBlock: error.previousDecisionBlock,
            issues: [...error.issues],
        };
    }
    return {
        kind: "schema_invalid",
        ...buildRepairContextPayload(rawOutput, error),
    };
}
function buildStructuredDraftRepairContext(rawOutput, error, kind) {
    return {
        kind,
        ...buildRepairContextPayload(rawOutput, error),
    };
}
function buildRepairContextPayload(rawOutput, error) {
    const decisionBlock = error.decisionBlock?.trim() ?? "";
    const previousDecisionBlock = decisionBlock.length > 0
        ? decisionBlock
        : (extractDecisionBlock(rawOutput) ?? rawOutput.trim());
    const issues = error.validationIssues.length > 0
        ? error.validationIssues
        : error.issues.length > 0
            ? error.issues
            : extractRepairIssues(error);
    return {
        previousDecisionBlock,
        issues,
    };
}
function extractRepairIssues(error) {
    if (error.issues.length > 0) {
        return error.issues;
    }
    return [{ path: [], code: "decision_invalid", message: error.message }];
}
function extractDecisionBlock(rawOutput) {
    return (0, decision_output_1.extractPreferredWorkerDecisionBlock)(rawOutput);
}
function emitRepairRetryStartDiagnostic(input, repairContext, error, attemptCount = 2, maxAttempts = 2) {
    input.onStderrChunk?.(`${[
        `${CLI_LOG_PREFIX} worker repair retry started kind=${repairContext.kind} attempt=${attemptCount}/${maxAttempts}`,
        `${CLI_LOG_PREFIX} repair issues: ${formatRepairIssuesForDiagnostic(repairContext.issues, error.message)}`,
    ].join("\n")}\n`);
}
function emitRepairRetryEndDiagnostic(input, repairContext, result, error, attemptCount = 2, maxAttempts = 2) {
    const suffix = result === "failed"
        ? ` error=${error instanceof Error ? error.message : String(error)}`
        : "";
    input.onStderrChunk?.(`${CLI_LOG_PREFIX} worker repair retry ${result} kind=${repairContext.kind} attempt=${attemptCount}/${maxAttempts}${suffix}\n`);
}
function emitStructuredDraftRepairRetryStartDiagnostic(input, label, repairContext, errorMessage) {
    input.onStderrChunk?.(`${[
        `${CLI_LOG_PREFIX} ${label} repair retry started kind=${repairContext.kind} attempt=2/2`,
        `${CLI_LOG_PREFIX} repair issues: ${formatRepairIssuesForDiagnostic(repairContext.issues, errorMessage)}`,
    ].join("\n")}\n`);
}
function emitStructuredDraftRepairRetryEndDiagnostic(input, label, repairContext, result, error) {
    const suffix = result === "failed"
        ? ` error=${error instanceof Error ? error.message : String(error)}`
        : "";
    input.onStderrChunk?.(`${CLI_LOG_PREFIX} ${label} repair retry ${result} kind=${repairContext.kind} attempt=2/2${suffix}\n`);
}
function formatRepairIssuesForDiagnostic(issues, fallback) {
    if (issues.length === 0) {
        return fallback;
    }
    return issues
        .map((issue) => {
        const path = issue.path.join(".") || "(root)";
        return `${path} [${issue.code}]: ${issue.message}`;
    })
        .join("; ");
}
function validateDecision(decision, validator) {
    if (!validator) {
        return;
    }
    try {
        validator(decision);
    }
    catch (error) {
        if (!(0, state_1.isWorkflowDecisionValidationError)(error)) {
            throw error;
        }
        throw new WorkerDecisionSemanticError(error.message, {
            issues: [
                {
                    path: error.path,
                    code: error.code,
                    message: error.message,
                },
            ],
            previousDecisionBlock: JSON.stringify(decision),
            repairEligible: error.repairEligible,
            cause: error,
        });
    }
}
function wrapWorkerRunError(command, error, locale = catalog_1.DEFAULT_APP_LOCALE) {
    if ((0, errors_1.isWorkerFailureError)(error)) {
        return error;
    }
    if (error instanceof command_1.CommandError) {
        if (error.timedOut) {
            return new errors_1.WorkerTimeoutError((0, catalog_1.translate)(locale, "worker.error.commandTimedOut", { command }), {
                cause: error,
                attempted: true,
                timeoutKind: error.timeoutKind,
                publicMessage: (0, catalog_1.translate)(locale, "worker.error.timeout"),
            });
        }
        const classified = classifyCommandFailure(command, error, locale);
        if (classified) {
            return classified;
        }
        return new errors_1.WorkerExecutionError((0, catalog_1.translate)(locale, "worker.error.commandFailedExitCode", {
            command,
            exitCode: error.exitCode,
        }), {
            cause: error,
            attempted: true,
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.executionFailed"),
        });
    }
    if (error instanceof Error) {
        if ("code" in error && error.code === "ENOENT") {
            return new errors_1.WorkerUnavailableError((0, catalog_1.translate)(locale, "worker.error.failedToLaunch", {
                command,
                message: error.message,
            }), {
                cause: error,
                publicMessage: (0, catalog_1.translate)(locale, "worker.error.unavailable"),
            });
        }
        return new errors_1.WorkerExecutionError((0, catalog_1.translate)(locale, "worker.error.failedToLaunch", {
            command,
            message: error.message,
        }), {
            cause: error,
            attempted: false,
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.executionFailed"),
        });
    }
    return new errors_1.WorkerExecutionError((0, catalog_1.translate)(locale, "worker.error.failedToLaunchNoDetails", { command }), {
        attempted: false,
        publicMessage: (0, catalog_1.translate)(locale, "worker.error.executionFailed"),
    });
}
function classifyWorkerOutputFailure(command, rawOutput, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const quotaFailure = extractStructuredQuotaFailure(rawOutput);
    if (quotaFailure === null) {
        return null;
    }
    return new errors_1.WorkerQuotaExhaustedError((0, catalog_1.translate)(locale, "worker.error.commandQuotaExhausted", { command }), {
        attempted: true,
        cooldownUntil: quotaFailure.cooldownUntil ?? parseCooldownUntil(rawOutput),
        publicMessage: (0, catalog_1.translate)(locale, "worker.error.quotaExhausted"),
    });
}
function buildWorkerEnvironment(authProfile, baseEnv, locale = catalog_1.DEFAULT_APP_LOCALE, credentialStore) {
    if (!authProfile) {
        return undefined;
    }
    switch (authProfile.kind) {
        case "codex-home":
        case "claude-cli":
            return (0, environment_1.buildAuthProfileEnvironment)(authProfile, baseEnv) ?? undefined;
        case "codex-env-var": {
            const value = baseEnv[authProfile.envVarName]?.trim();
            if (!value) {
                throw new errors_1.WorkerAuthInvalidError((0, catalog_1.translate)(locale, "worker.error.missingRequiredEnvVar", {
                    envVarName: authProfile.envVarName,
                    signIn: (0, store_1.formatAuthAccountDisplay)(authProfile, [], locale),
                }), {
                    publicMessage: (0, catalog_1.translate)(locale, "worker.error.authInvalid"),
                });
            }
            return ((0, environment_1.buildAuthProfileEnvironment)(authProfile, baseEnv, credentialStore) ??
                undefined);
        }
        case "claude-api-key": {
            const env = (0, environment_1.buildAuthProfileEnvironment)(authProfile, baseEnv, credentialStore);
            if (!env) {
                throw new errors_1.WorkerAuthInvalidError((0, catalog_1.translate)(locale, "worker.error.missingStoredCredential", {
                    signIn: (0, store_1.formatAuthAccountDisplay)(authProfile, [], locale),
                }), {
                    publicMessage: (0, catalog_1.translate)(locale, "worker.error.authInvalid"),
                });
            }
            return env;
        }
        case "claude-env-var": {
            const value = baseEnv[authProfile.envVarName]?.trim();
            if (!value) {
                throw new errors_1.WorkerAuthInvalidError((0, catalog_1.translate)(locale, "worker.error.missingRequiredEnvVar", {
                    envVarName: authProfile.envVarName,
                    signIn: (0, store_1.formatAuthAccountDisplay)(authProfile, [], locale),
                }), {
                    publicMessage: (0, catalog_1.translate)(locale, "worker.error.authInvalid"),
                });
            }
            return ((0, environment_1.buildAuthProfileEnvironment)(authProfile, baseEnv, credentialStore) ??
                undefined);
        }
    }
}
function classifyCommandFailure(command, error, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const text = [error.stderr, error.stdout].filter(Boolean).join("\n");
    const structuredQuotaFailure = extractStructuredQuotaFailure(text);
    if (structuredQuotaFailure !== null) {
        return new errors_1.WorkerQuotaExhaustedError((0, catalog_1.translate)(locale, "worker.error.commandQuotaExhausted", { command }), {
            cause: error,
            attempted: true,
            cooldownUntil: structuredQuotaFailure.cooldownUntil ?? parseCooldownUntil(text),
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.quotaExhausted"),
        });
    }
    const cooldownUntil = parseCooldownUntil(text);
    if (looksLikeCapacityFailure(text)) {
        return new errors_1.WorkerUnavailableError((0, catalog_1.translate)(locale, "worker.error.commandAtCapacity", { command }), {
            cause: error,
            attempted: true,
            cooldownUntil,
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.capacity"),
        });
    }
    if (looksLikeQuotaFailure(text)) {
        return new errors_1.WorkerQuotaExhaustedError((0, catalog_1.translate)(locale, "worker.error.commandQuotaExhausted", { command }), {
            cause: error,
            attempted: true,
            cooldownUntil,
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.quotaExhausted"),
        });
    }
    if (looksLikeAuthFailure(text)) {
        return new errors_1.WorkerAuthInvalidError((0, catalog_1.translate)(locale, "worker.error.commandAuthInvalid", { command }), {
            cause: error,
            attempted: true,
            publicMessage: (0, catalog_1.translate)(locale, "worker.error.authInvalid"),
        });
    }
    return null;
}
function looksLikeCapacityFailure(text) {
    const normalized = text.toLowerCase();
    return (normalized.includes("selected model is at capacity") ||
        normalized.includes("model is at capacity") ||
        (normalized.includes("at capacity") && normalized.includes("model")));
}
function looksLikeQuotaFailure(text) {
    const normalized = text.toLowerCase();
    return (normalized.includes("quota exhausted") ||
        normalized.includes("quota is exhausted") ||
        normalized.includes("rate limit") ||
        normalized.includes("retry-after") ||
        normalized.includes("retry after") ||
        normalized.includes("try again in") ||
        normalized.includes("too many requests") ||
        normalized.includes("insufficient balance") ||
        normalized.includes("usage limit"));
}
function looksLikeAuthFailure(text) {
    const normalized = text.toLowerCase();
    return (normalized.includes("authentication failed") ||
        normalized.includes("auth invalid") ||
        normalized.includes("invalid api key") ||
        normalized.includes("invalid token") ||
        normalized.includes("not logged in") ||
        normalized.includes("please login") ||
        normalized.includes("please log in") ||
        normalized.includes("unauthorized") ||
        normalized.includes("forbidden"));
}
function extractStructuredQuotaFailure(rawOutput) {
    let fallbackCooldownUntil = null;
    for (const line of rawOutput.split(/\r?\n/)) {
        const record = parseJsonRecord(line);
        if (record === null) {
            continue;
        }
        const rateLimitInfo = record.rateLimitInfo;
        const resetCooldownUntil = parseEpochTimestamp(rateLimitInfo?.resetsAt);
        if (record.type === "rate_limit_event" &&
            rateLimitInfo?.status === "rejected") {
            return {
                cooldownUntil: resetCooldownUntil,
            };
        }
        if (resetCooldownUntil !== null) {
            fallbackCooldownUntil = resetCooldownUntil;
        }
        if (record.isError === true && record.apiErrorStatus === 429) {
            return { cooldownUntil: fallbackCooldownUntil };
        }
        if (record.error === "rate_limit" && typeof record.type === "string") {
            return { cooldownUntil: fallbackCooldownUntil };
        }
    }
    return null;
}
function parseJsonRecord(line) {
    const trimmed = line.trim();
    if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
        return null;
    }
    try {
        return readOutputRecord(JSON.parse(trimmed));
    }
    catch {
        return null;
    }
}
function readOutputRecord(value) {
    if (!isPlainObject(value)) {
        return null;
    }
    return {
        type: readObjectProperty(value, "type"),
        rateLimitInfo: readRateLimitInfo(readObjectProperty(value, "rate_limit_info")),
        isError: readObjectProperty(value, "is_error"),
        apiErrorStatus: readObjectProperty(value, "api_error_status"),
        error: readObjectProperty(value, "error"),
    };
}
function readRateLimitInfo(value) {
    if (!isPlainObject(value)) {
        return null;
    }
    return {
        status: readObjectProperty(value, "status"),
        resetsAt: readObjectProperty(value, "resetsAt"),
    };
}
function isPlainObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readObjectProperty(value, key) {
    return Reflect.get(value, key);
}
function parseEpochTimestamp(value) {
    if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
        return null;
    }
    const epochMs = value >= 1_000_000_000_000 ? value : value * 1_000;
    return new Date(epochMs).toISOString();
}
function parseCooldownUntil(text, now = new Date()) {
    const isoMatch = text.match(/\b(20\d{2}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)\b/);
    if (isoMatch?.[1]) {
        return isoMatch[1];
    }
    const epochMatch = text.match(/\b(?:reset|retry[- ]after)\b[^0-9]*(\d{10,13})\b/i);
    if (epochMatch?.[1]) {
        const rawValue = Number.parseInt(epochMatch[1], 10);
        const epochMs = epochMatch[1].length === 13 ? rawValue : rawValue * 1_000;
        return new Date(epochMs).toISOString();
    }
    const retryAfterMatch = text.match(/retry[- ]after[: ]+(\d+)(?:\s*(seconds?|secs?|s))?/i);
    if (retryAfterMatch?.[1]) {
        return new Date(now.getTime() + Number.parseInt(retryAfterMatch[1], 10) * 1_000).toISOString();
    }
    const relativeMatch = text.match(/(?:try again|retry|reset)[^\n]*?\bin\s+(\d+)\s*(seconds?|secs?|minutes?|mins?|hours?|hrs?)/i);
    if (!relativeMatch?.[1] || !relativeMatch[2]) {
        return null;
    }
    const amount = Number.parseInt(relativeMatch[1], 10);
    const unit = relativeMatch[2].toLowerCase();
    const multiplier = unit.startsWith("hour")
        ? 60 * 60 * 1_000
        : unit.startsWith("min")
            ? 60 * 1_000
            : 1_000;
    return new Date(now.getTime() + amount * multiplier).toISOString();
}
