/**
 * @module        projectPipelineAdapter
 * @description   流水线 YAML 配置与引擎之间的适配层
 *
 * @author        eddy
 * @organization  wykj
 * @ownership     wykj/eddy
 *
 * @created       2026-04-02
 * @updated       2026-04-03
 *
 * @role          util
 * @layer         core
 * @boundedContext pipeline
 */
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { resolve } from 'node:path';
import { parse as parseYaml } from 'yaml';
// ─── Defaults (exact match of current hardcoded behavior) ────────
const DEFAULT_STATES = {
    planning: 'Planning',
    backlog: 'Backlog',
    ready: 'Todo',
    active: 'Inprogress',
    review: 'QA',
    done: 'Done',
};
const DEFAULT_AUXILIARY_LABELS = [
    'BLOCKED', 'NEEDS-FIX', 'CONFLICT', 'WAITING-CONFIRMATION', 'STALE-RUNTIME', 'CLAIMED',
];
function defaultStages(_config) {
    return [
        {
            name: 'develop',
            triggerState: 'Todo',
            activeState: 'Inprogress',
            agent: 'claude',
            completion: 'git-evidence',
            onCompleteState: 'QA',
            onFailLabel: 'NEEDS-FIX',
            onFailComment: 'Worker failed. Marked as NEEDS-FIX.',
        },
        {
            name: 'integrate',
            triggerState: 'QA',
            activeState: 'QA',
            agent: 'claude',
            completion: 'fast-forward-merge',
            onCompleteState: 'Done',
            queue: 'fifo',
        },
    ];
}
// ─── Adapter ────────────────────────────────────────────────────
export class ProjectPipelineAdapter {
    settings;
    constructor(config, projectDir) {
        const yamlConfig = loadProjectPipelineYaml(config.PROJECT_NAME, projectDir);
        if (yamlConfig) {
            this.settings = buildFromYaml(yamlConfig, config);
        }
        else {
            this.settings = buildDefaults(config);
        }
    }
    /** Whether git operations are enabled */
    get gitEnabled() {
        return this.settings.gitEnabled;
    }
    /** Get card state names */
    get states() {
        return this.settings.states;
    }
    /** Get all stage definitions */
    get stages() {
        return this.settings.stages;
    }
    /** Find stage by name */
    getStage(name) {
        return this.settings.stages.find(s => s.name === name);
    }
    /** Find stage by trigger state (which PM state activates it) */
    getStageByTrigger(pmState) {
        return this.settings.stages.find(s => s.triggerState === pmState);
    }
    /** Get the first stage (responsible for prepare: branch + worktree) */
    get firstStage() {
        return this.settings.stages[0];
    }
    /** Get the last stage (responsible for release: worktree cleanup) */
    get lastStage() {
        return this.settings.stages[this.settings.stages.length - 1];
    }
    /**
     * @deprecated Use firstStage instead. Kept for backward compatibility during migration.
     */
    get developStage() {
        return this.settings.stages[0];
    }
    /**
     * @deprecated Use lastStage or getStage() instead. Kept for backward compatibility during migration.
     */
    get integrateStage() {
        return this.settings.stages.find(s => s.queue === 'fifo')
            || this.settings.stages[this.settings.stages.length - 1];
    }
    /** Find the stage whose activeState matches the given PM state */
    getStageByActiveState(pmState) {
        return this.settings.stages.find(s => s.activeState === pmState);
    }
    /** All states where cards are "active" (not done, not backlog) */
    get activeStates() {
        return this.settings.activeStates;
    }
    /** Labels to clean when card goes back to backlog */
    get auxiliaryLabels() {
        return this.settings.auxiliaryLabels;
    }
    /** Derive PM state from lease phase */
    derivePmState(leasePhase, currentPmState) {
        const s = this.settings.states;
        switch (leasePhase) {
            case 'queued':
            case 'preparing':
                return s.ready;
            case 'coding':
                return s.active;
            case 'merging':
            case 'resolving_conflict':
            case 'closing':
                return s.review;
            case 'waiting_confirmation':
                return currentPmState === s.review ? s.review : s.active;
            default:
                return s.ready;
        }
    }
}
// ─── YAML Loading ───────────────────────────────────────────────
/** Resolve pipelines dir: ~/.coral/projects/<name>/pipelines/ with legacy fallback */
function resolvePipelinesDir(projectName, projectDir) {
    const coralDir = resolve(homedir(), '.coral', 'projects', projectName, 'pipelines');
    if (existsSync(coralDir))
        return coralDir;
    // Legacy fallback: <repo>/.sps/pipelines/
    if (projectDir) {
        const legacyDir = resolve(projectDir, '.sps', 'pipelines');
        if (existsSync(legacyDir))
            return legacyDir;
    }
    return null;
}
/** Path to the active pipeline marker file */
function activePipelinePath(projectName) {
    return resolve(homedir(), '.coral', 'projects', projectName, 'active-pipeline');
}
/** Read the active pipeline name */
export function getActivePipelineName(projectName) {
    try {
        const marker = readFileSync(activePipelinePath(projectName), 'utf-8').trim();
        return marker || null;
    }
    catch {
        return null;
    }
}
/** Set the active pipeline name */
export function setActivePipeline(projectName, name) {
    const dir = resolve(homedir(), '.coral', 'projects', projectName);
    if (!existsSync(dir))
        mkdirSync(dir, { recursive: true });
    writeFileSync(activePipelinePath(projectName), name + '\n');
}
function loadProjectPipelineYaml(projectName, projectDir) {
    const pipelinesDir = resolvePipelinesDir(projectName, projectDir);
    if (!pipelinesDir)
        return null;
    // Check for explicitly activated pipeline
    const activeName = getActivePipelineName(projectName);
    if (activeName) {
        for (const ext of ['.yaml', '.yml']) {
            const filePath = resolve(pipelinesDir, activeName + ext);
            if (existsSync(filePath)) {
                try {
                    const parsed = parseYaml(readFileSync(filePath, 'utf-8'));
                    if (parsed?.mode === 'project')
                        return parsed;
                }
                catch { /* skip unparseable */ }
            }
        }
    }
    // Fallback: find first mode:project YAML
    try {
        const files = readdirSync(pipelinesDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
        for (const file of files) {
            try {
                const raw = readFileSync(resolve(pipelinesDir, file), 'utf-8');
                const parsed = parseYaml(raw);
                if (parsed?.mode === 'project')
                    return parsed;
            }
            catch { /* skip unparseable */ }
        }
    }
    catch { /* dir unreadable */ }
    return null;
}
function buildFromYaml(yaml, config) {
    // Card states: merge YAML over defaults (pm.card_states is optional)
    const states = {
        ...DEFAULT_STATES,
        ...(yaml.pm?.card_states || yaml.states || {}),
    };
    // Stages: build from YAML or use defaults
    let stages;
    if (yaml.stages && Array.isArray(yaml.stages) && yaml.stages.length > 0) {
        const yamlStages = yaml.stages;
        // Auto-generate stage states from on_complete chain
        // First stage trigger = states.ready, active = 'Inprogress' (or explicit card_state)
        // Subsequent stages: trigger = previous stage's on_complete target
        stages = yamlStages.map((s, idx) => {
            // Determine trigger state
            let triggerState;
            if (s.trigger) {
                triggerState = parseTrigger(s.trigger) || states.ready;
            }
            else if (idx === 0) {
                triggerState = states.ready;
            }
            else {
                // Auto: previous stage's on_complete target
                const prevComplete = parseOnComplete(yamlStages[idx - 1].on_complete, '');
                triggerState = prevComplete || states.ready;
            }
            // Determine active state (must differ from trigger to show progress)
            const activeState = s.card_state || (idx === 0 ? states.active : triggerState);
            // Default on_complete: next stage trigger or Done for last
            const nextTrigger = idx < yamlStages.length - 1
                ? (parseTrigger(yamlStages[idx + 1].trigger) || yamlStages[idx + 1].card_state || states.done)
                : states.done;
            // Parse on_fail halt config
            const onFailRaw = s.on_fail;
            const halt = onFailRaw?.halt !== false; // default true
            return {
                name: s.name,
                triggerState,
                activeState,
                agent: 'claude', // claude is the only supported CLI; YAML agent: field is ignored
                profile: s.profile,
                completion: s.completion || 'exit-code', // Default: exit-code (single worker model)
                onCompleteState: parseOnComplete(s.on_complete, nextTrigger),
                onFailLabel: parseOnFailLabel(s.on_fail),
                onFailComment: parseOnFailComment(s.on_fail),
                queue: undefined, // Ignored: single worker, no queue
                timeout: s.timeout,
                halt, // Failure halt: default true
            };
        });
    }
    else {
        stages = defaultStages(config);
        stages[0].triggerState = states.ready;
        stages[0].activeState = states.active;
        stages[0].onCompleteState = states.review;
        stages[1].triggerState = states.review;
        stages[1].activeState = states.review;
        stages[1].onCompleteState = states.done;
    }
    // Derive activeStates from stages (all non-done states)
    const activeStateSet = new Set();
    if (states.planning)
        activeStateSet.add(states.planning);
    activeStateSet.add(states.backlog);
    activeStateSet.add(states.ready);
    for (const stage of stages) {
        if (stage.triggerState)
            activeStateSet.add(stage.triggerState);
        if (stage.activeState)
            activeStateSet.add(stage.activeState);
    }
    const activeStates = Array.from(activeStateSet);
    // Git mode: default true (commit+push), false (no git ops)
    const gitEnabled = yaml.git !== false;
    return {
        gitEnabled,
        states,
        stages,
        activeStates,
        auxiliaryLabels: DEFAULT_AUXILIARY_LABELS,
    };
}
function buildDefaults(config) {
    return {
        gitEnabled: true,
        states: { ...DEFAULT_STATES },
        stages: defaultStages(config),
        activeStates: ['Planning', 'Backlog', 'Todo', 'Inprogress', 'QA'],
        auxiliaryLabels: DEFAULT_AUXILIARY_LABELS,
    };
}
// ─── YAML Field Parsers ─────────────────────────────────────────
/** Parse "card_enters 'Todo'" → "Todo" */
function parseTrigger(raw) {
    if (!raw)
        return undefined;
    const match = raw.match(/card_enters\s+["']?(\S+?)["']?\s*$/);
    return match ? match[1] : raw;
}
/** Parse on_complete to target state. fallbackState is used when on_complete is not defined. */
function parseOnComplete(raw, fallbackState) {
    if (!raw)
        return fallbackState;
    if (typeof raw === 'string') {
        const match = raw.match(/move_card\s+["']?(\S+?)["']?\s*$/);
        return match ? match[1] : raw;
    }
    if (typeof raw === 'object' && raw.action) {
        const match = raw.action.match(/move_card\s+["']?(\S+?)["']?\s*$/);
        return match ? match[1] : fallbackState;
    }
    return fallbackState;
}
/** Parse on_fail to label */
function parseOnFailLabel(raw) {
    if (!raw)
        return 'NEEDS-FIX';
    if (typeof raw === 'string') {
        const match = raw.match(/label\s+["']?(\S+?)["']?\s*$/);
        return match ? match[1] : undefined;
    }
    if (typeof raw === 'object' && raw.action) {
        const match = raw.action.match(/label\s+["']?(\S+?)["']?\s*$/);
        return match ? match[1] : undefined;
    }
    return undefined;
}
/** Parse on_fail to comment */
function parseOnFailComment(raw) {
    if (typeof raw === 'object' && raw.comment)
        return raw.comment;
    return undefined;
}
//# sourceMappingURL=projectPipelineAdapter.js.map