/**
 * remote-pipeline.ts — Remote execution flow for `ailf run --remote`.
 *
 * Reads local `.ailf/tasks/` YAML, validates locally via Zod schemas,
 * submits to the AILF API, polls for completion, and writes the same
 * output artifacts as local mode (score-summary.json, report.md,
 * job-metadata.json).
 *
 * This module is the CLI-side counterpart of the API's POST /v1/pipeline
 * endpoint. The CLI and API are published from the same monorepo, so the
 * request/response shapes are always in sync.
 *
 * @see packages/eval/src/adapters/api-client/ — HTTP client + request builder
 * @see docs/design-docs/cli-as-api-client.md — design doc
 */
import { ZodError } from "zod";
import { ApiClient, buildRemoteRequest, createProgressDisplay, formatJobError, NoRunnableTasksError, resolveTasksDir, } from "../adapters/api-client/index.js";
import { writeRemoteResults } from "./remote-results.js";
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Run the evaluation pipeline in remote mode.
 *
 * 1. Validate we have an API key
 * 2. Find and validate local tasks (fail-fast with Zod errors)
 * 3. Build the PipelineRequest payload
 * 4. Submit to the AILF API
 * 5. Poll for completion with progress display
 * 6. Write output artifacts (score-summary.json, report.md, job-metadata.json)
 */
export async function runRemotePipeline(opts, rootDir) {
    // 1. Validate API key
    if (!opts.apiKey) {
        console.error("❌ AILF_API_KEY is required for remote evaluation.");
        console.error("");
        console.error("   Set it in your environment:");
        console.error("   export AILF_API_KEY=ailf_live_sk_...");
        process.exit(2);
    }
    const client = new ApiClient({
        apiKey: opts.apiKey,
        baseUrl: opts.apiUrl,
    });
    // 2. Find tasks directory
    const tasksDir = resolveTasksDir(rootDir, opts.repoTasksPath);
    // 3. Build request from local state (validates locally first)
    const configSlice = toConfigSlice(opts);
    let request;
    let taskCount;
    try {
        const result = await buildRemoteRequest({
            tasksDir,
            config: configSlice,
        });
        request = result.request;
        taskCount = result.taskCount;
    }
    catch (err) {
        if (err instanceof ZodError) {
            console.error("❌ Task validation failed:\n");
            for (const issue of err.issues) {
                console.error(`   ${issue.path.join(".")}: ${issue.message}`);
            }
            console.error("");
            console.error("💡 Fix the issues above in your .ailf/tasks/ YAML files.");
            process.exit(2);
        }
        if (err instanceof NoRunnableTasksError) {
            console.error(`❌ ${err.message}`);
            process.exit(2);
        }
        throw err;
    }
    console.log(`📦 Found ${taskCount} task(s) in ${tasksDir}`);
    // 4. Submit to API
    const submission = await client.submitPipeline(request);
    console.log(`🚀 Submitted job: ${submission.jobId}`);
    if (submission.estimatedDurationMs) {
        const mins = Math.ceil(submission.estimatedDurationMs / 60_000);
        console.log(`⏱️  Estimated duration: ~${mins} minute(s)`);
    }
    // 5. Poll for completion with progress display
    const job = await client.waitForCompletion(submission.jobId, {
        onProgress: createProgressDisplay(),
    });
    // 6. Handle result
    if (job.status !== "completed") {
        console.error("");
        console.error(formatJobError(job));
        process.exit(1);
    }
    // 7. Fetch and write output artifacts. A `completed` job that carries
    //    `job.error` is a degraded completion (DOC-2121 RC-3): a configured
    //    optional step failed end-to-end. Artifacts still write so the caller
    //    keeps useful local state, but the CLI exits non-zero so external
    //    `--remote` consumers don't mistake the placeholder for success.
    const outcome = await writeRemoteResults(client, job, {
        outputDir: opts.outputDir,
        outputPath: opts.outputPath,
        apiUrl: opts.apiUrl,
    });
    if (outcome.degraded) {
        process.exit(1);
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Extract the subset of ResolvedOptions needed for building a remote request.
 * This mapping keeps remote-pipeline.ts decoupled from the full ResolvedConfig.
 */
function toConfigSlice(opts) {
    return {
        mode: opts.mode,
        variant: opts.variant,
        debug: opts.debug,
        areas: opts.areaOption
            ?.split(",")
            .map((s) => s.trim())
            .filter(Boolean),
        tasks: opts.taskOption
            ?.split(",")
            .map((s) => s.trim())
            .filter(Boolean),
        changedDocs: opts.changedDocsOption
            ?.split(",")
            .map((s) => s.trim())
            .filter(Boolean),
        source: opts.source,
        compareEnabled: opts.compareEnabled,
        compareThreshold: opts.compareThreshold,
        // Only send publish preference when the user explicitly passed
        // --publish or --no-publish. When omitted, the API applies its own
        // default (publish: true for API-triggered jobs with a jobId).
        publishEnabled: opts.publishExplicit ? opts.publishEnabled : undefined,
        publishTag: opts.publishTag,
        concurrency: opts.concurrency,
        baseUrlOverride: opts.baseUrlOverride,
        datasetOverride: opts.datasetOverride,
        projectIdOverride: opts.projectIdOverride,
        perspectiveOverride: opts.perspectiveOverride,
        studioOriginOverride: opts.studioOriginOverride,
        graderContext: opts.graderContext,
        graderReplications: opts.graderReplications,
        borderlineReplications: opts.borderlineReplications,
        gapAnalysisEnabled: opts.gapAnalysisEnabled,
        noRemoteCache: opts.noRemoteCache,
        // D0037 / W0069 caller envelope overrides — flags override env vars
        // inside buildCallerEnvelope(), which also merges AILF_* defaults.
        classificationOption: opts.classificationOption,
        ownerTeamOption: opts.ownerTeamOption,
        ownerIndividualOption: opts.ownerIndividualOption,
        purposeOption: opts.purposeOption,
        labelOptions: opts.labelOptions,
    };
}
