/**
 * Fixture resolver — resolves fixture references into content for compilation.
 *
 * Handles document fixtures (fetched from Sanity or local files),
 * file fixtures (read from disk), and inline fixtures (embedded in
 * task definitions). Resolved content is injected into the TaskNode's
 * VariableEnvelope for the compiler to use.
 *
 * Currently supports the existing fixture patterns:
 * - `file://contexts/canonical/<id>.md` → read from local fs
 * - Inline `vars.docs` strings → used as-is
 * - Canonical doc references → resolved by DocFetcher port
 *
 * Future phases will add URI scheme resolution (repo://, sanity://, etc.)
 * as described in the fixtures-artifacts design doc.
 *
 * @see docs/design-docs/architecture-overhaul/fixtures-artifacts.md
 * @see docs/archive/exec-plans/architecture-overhaul/phase-2-config-compiler.md
 */
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { simpleHash } from "./hash.js";
/**
 * Resolve fixtures for a task definition.
 *
 * Reads `vars.docs` and canonical doc references, resolves them to
 * content, and updates the variable envelope.
 */
export function resolveTaskFixtures(task, currentVars, options) {
    const fixtures = new Map();
    const warnings = [];
    const updatedValues = { ...currentVars.values };
    const updatedProvenance = { ...currentVars.provenance };
    // Resolve file:// references in vars
    for (const [key, value] of Object.entries(updatedValues)) {
        if (typeof value === "string" && value.startsWith("file://")) {
            const resolved = resolveFileRef(value, options.rootDir);
            if (resolved.ok) {
                updatedValues[key] = resolved.content;
                updatedProvenance[key] = {
                    hash: simpleHash(resolved.content),
                    resolvedAt: new Date().toISOString(),
                    source: { fixtureId: value, type: "fixture" },
                };
                fixtures.set(value, {
                    content: resolved.content,
                    contentHash: simpleHash(resolved.content),
                    id: value,
                    name: value.replace("file://", ""),
                    type: "fetched",
                });
            }
            else {
                warnings.push(resolved.error);
            }
        }
    }
    // Resolve canonical doc context if any — extract docs from the mode-specific
    // context field (context.docs exists on literacy, mcp-server, agent-harness,
    // and knowledge-probe variants)
    const contextDocs = "context" in task && task.context?.docs ? task.context.docs : [];
    if (contextDocs.length > 0) {
        const docFixtureId = `canonical-docs:${task.id}`;
        // Canonical docs are resolved at runtime by the DocFetcher port.
        // At compile time, we create a placeholder fixture that signals
        // the compiler to inject doc context at eval time.
        fixtures.set(docFixtureId, {
            content: null, // Deferred — resolved at eval time
            id: docFixtureId,
            name: `Canonical docs for ${task.id}`,
            type: "fetched",
        });
    }
    return {
        fixtures,
        updatedVars: {
            declarations: currentVars.declarations,
            provenance: updatedProvenance,
            values: updatedValues,
        },
        warnings,
    };
}
/**
 * Resolve a `file://` reference to file content.
 */
function resolveFileRef(fileRef, rootDir) {
    const relativePath = fileRef.replace("file://", "");
    const absolutePath = resolve(rootDir, relativePath);
    // Path containment: prevent file://../../etc/passwd from reading outside rootDir
    const normalizedBase = resolve(rootDir) + "/";
    if (!absolutePath.startsWith(normalizedBase) &&
        absolutePath !== resolve(rootDir)) {
        return {
            ok: false,
            error: `Path traversal detected: "${fileRef}" resolves outside rootDir "${rootDir}"`,
        };
    }
    if (!existsSync(absolutePath)) {
        return {
            ok: false,
            error: `Fixture file not found: ${absolutePath} (referenced as ${fileRef})`,
        };
    }
    try {
        const content = readFileSync(absolutePath, "utf-8");
        return { ok: true, content };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { ok: false, error: `Failed to read fixture ${fileRef}: ${msg}` };
    }
}
