/**
 * Adapter: Load task definitions from .ailf/tasks/ in an external repo.
 *
 * Supports two task file formats:
 *
 * 1. **TypeScript (.task.ts)** — Tasks authored with `defineTask()` from
 *    `@sanity/ailf`. These use `GeneralizedTaskDefinition` field names
 *    and are passed through with basic runtime guards.
 *
 * 2. **YAML (.yaml)** — Tasks using the canonical `GeneralizedTaskDefinition`
 *    field names (area, context.docs, assertions, prompt.text). Validated
 *    through the CanonicalTaskSchema Zod schema.
 *
 * All tasks use the single canonical shape — no mapping layer, no dual-shape
 * detection. The `mode` field defaults to "literacy" for YAML tasks that
 * omit it.
 *
 * @see packages/core/src/types/generalized-task.ts — canonical types
 * @see packages/core/src/ports/task-source.ts — TaskSource port
 */
import { existsSync, readdirSync, readFileSync } from "fs";
import { resolve } from "path";
import { load } from "js-yaml";
import { CANONICAL_EVAL_MODES } from "../../_vendor/ailf-shared/index.js";
import { filterByChangedDocs } from "./changed-docs-filter.js";
import { injectContextDocsIntoTasks } from "./inject-context-docs.js";
import { detectLegacyFieldNames, migratePromptShape, parseCanonicalTaskFile, } from "./repo-schemas.js";
import { discoverTsTaskFiles, loadTsTaskFile } from "./task-file-loader.js";
/** Set of canonical mode names for O(1) lookup */
const KNOWN_MODES = new Set(CANONICAL_EVAL_MODES);
export class RepoTaskSource {
    tasksDir;
    options;
    constructor(tasksDir, options = {}) {
        this.tasksDir = tasksDir;
        this.options = options;
    }
    async loadTasks(filter) {
        if (!existsSync(this.tasksDir)) {
            if (this.options.allowMissing)
                return [];
            throw new Error(`Repo tasks directory not found: ${this.tasksDir}\n` +
                "  Provide a valid path via --repo-tasks-path");
        }
        const yamlFiles = readdirSync(this.tasksDir)
            .filter((f) => (f.endsWith(".yaml") || f.endsWith(".yml")) && !f.startsWith("."))
            .sort();
        const tsFiles = discoverTsTaskFiles(this.tasksDir);
        if (yamlFiles.length === 0 && tsFiles.length === 0) {
            if (this.options.allowMissing)
                return [];
            throw new Error(`No task files found in ${this.tasksDir}\n` +
                "  Expected .ailf/tasks/*.yaml or .ailf/tasks/*.task.ts files");
        }
        const definitions = [];
        // Load YAML task files
        for (const file of yamlFiles) {
            const filePath = resolve(this.tasksDir, file);
            const raw = readFileSync(filePath, "utf-8");
            const parsed = load(raw);
            if (!Array.isArray(parsed)) {
                throw new Error(`${file} did not parse to an array of tasks. ` +
                    "Repo task files must contain a YAML array of task definitions.");
            }
            // Detect legacy field names (featureArea, canonicalDocs, assert, vars)
            // and surface helpful migration messages before Zod validation fails.
            const legacyWarnings = detectLegacyFieldNames(parsed, file);
            if (legacyWarnings.length > 0) {
                throw new Error(`${file} uses legacy field names from @sanity/ailf-tasks.\n` +
                    "Task files must use canonical GeneralizedTaskDefinition field names.\n\n" +
                    legacyWarnings.join("\n") +
                    "\n\nSee contributing-tasks.md for the canonical task format.");
            }
            // W0193: pre-migrate legacy prompt.vars.{task,docs,__featureArea}
            // to the canonical prompt.text + context.docs shape. Mode-agnostic —
            // every mode's TaskPromptSchema rejects reserved keys, so the shim
            // unblocks legacy tasks regardless of mode. Per-task deprecation
            // warning fires on stderr.
            const { migrated, warnings: deprecationWarnings } = migratePromptShape(parsed, file);
            for (const warning of deprecationWarnings) {
                console.warn(warning);
            }
            // Validate through canonical Zod schema
            let validated;
            try {
                validated = parseCanonicalTaskFile(migrated, file);
            }
            catch (err) {
                const msg = err instanceof Error ? err.message : String(err);
                throw new Error(`Failed to validate ${file}:\n${msg}`, { cause: err });
            }
            for (const task of validated) {
                if (passesFilter(task, filter)) {
                    definitions.push(task);
                }
            }
        }
        // Load TS task files (.task.ts / .task.js)
        for (const tsFile of tsFiles) {
            const loaded = await loadTsTaskFile(tsFile);
            const filename = tsFile.split("/").pop() ?? tsFile;
            for (const raw of loaded.tasks) {
                const task = raw;
                if (!task.id || typeof task.id !== "string") {
                    throw new Error(`Task in ${filename} is missing a valid "id" field`);
                }
                if (!task.mode || !KNOWN_MODES.has(task.mode)) {
                    throw new Error(`Task "${task.id}" in ${filename} has missing or unknown mode "${task.mode}". ` +
                        `Valid modes: ${[...KNOWN_MODES].join(", ")}`);
                }
                if (passesFilter(task, filter)) {
                    definitions.push(task);
                }
            }
        }
        return injectContextDocsIntoTasks(filterByChangedDocs(definitions, filter?.changedDocs), filter?.injectContextDocs);
    }
}
// ---------------------------------------------------------------------------
// Filter helper
// ---------------------------------------------------------------------------
/**
 * Apply standard task filtering. Used for both YAML and TS tasks.
 */
function passesFilter(task, filter) {
    // Area filter
    if (filter?.areas &&
        filter.areas.length > 0 &&
        (!task.area ||
            !filter.areas
                .map((a) => a.toLowerCase())
                .includes(task.area.toLowerCase()))) {
        return false;
    }
    // Task ID filter
    if (filter?.taskIds &&
        filter.taskIds.length > 0 &&
        !filter.taskIds.includes(task.id)) {
        return false;
    }
    // Status filter — unified lifecycle control
    const effectiveStatus = task.status ?? "active";
    const isTargetedById = filter?.taskIds && filter.taskIds.includes(task.id);
    if (effectiveStatus === "archived")
        return false;
    if (effectiveStatus === "paused" && !isTargetedById)
        return false;
    if (effectiveStatus === "draft" &&
        !isTargetedById &&
        !filter?.includeDrafts) {
        return false;
    }
    // Tag filter
    if (filter?.tags &&
        filter.tags.length > 0 &&
        (!task.tags || !task.tags.some((t) => filter.tags.includes(t)))) {
        return false;
    }
    return true;
}
