import { promises as fs } from 'node:fs';
import { join } from 'node:path';
import { atomicWriteFile } from '../utils/atomic-write.js';
import { canonicalJsonStringify, parseWorkflowDefinition, } from './definition.js';
import { ensureRunDir } from './runs-dir.js';
import { logger } from '../utils/logger.js';
export function workflowDefinitionSearchPaths(workflowId) {
    const home = process.env.HOME;
    return [
        join(process.cwd(), 'workflows', `${workflowId}.workflow.json`),
        join(home ?? '', '.botmux', 'workflows', `${workflowId}.workflow.json`),
    ];
}
export async function loadWorkflowDefinition(workflowId) {
    const paths = workflowDefinitionSearchPaths(workflowId);
    for (const path of paths) {
        try {
            const raw = await fs.readFile(path, 'utf-8');
            return parseWorkflowDefinition(JSON.parse(raw));
        }
        catch (err) {
            if (err.code === 'ENOENT')
                continue;
            throw new Error(`Failed to load workflow '${workflowId}' from ${path}: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    throw new Error(`Workflow '${workflowId}' not found. Looked in:\n${paths.map((p) => `- ${p}`).join('\n')}`);
}
export async function snapshotWorkflowDefinition(runId, def, opts = {}) {
    const dir = await getOrEnsureRunDir(runId, opts);
    const path = join(dir, 'workflow.json');
    await atomicWriteFile(path, canonicalJsonStringify(def));
    return path;
}
/**
 * Load the workflow definition snapshot from a run's directory.  Each run
 * persists `workflow.json` next to `events.ndjson` so consumers that come
 * in via run id alone (Lark card callbacks, dashboard resolve) can recover
 * the definition without needing the catalog (which may drift).
 *
 * Returns null when the snapshot is missing or unreadable — callers can
 * still operate (e.g. resolveWait without ctx.def degrades to v0.1
 * approve/reject semantics).  The fallback is the *intended* design, but
 * we log non-ENOENT failures so an unexpected parse/IO error doesn't go
 * silently null and confuse debugging (ENOENT stays silent — missing
 * snapshot is a normal state for legacy v0.1 runs that predate this file).
 */
export async function readWorkflowDefinitionFromRunDir(runDir) {
    const path = join(runDir, 'workflow.json');
    try {
        const raw = await fs.readFile(path, 'utf-8');
        return parseWorkflowDefinition(JSON.parse(raw));
    }
    catch (err) {
        if (err.code !== 'ENOENT') {
            logger.warn?.(`readWorkflowDefinitionFromRunDir: ${path} unreadable — caller falls back to v0.1 wait semantics: ${err instanceof Error ? err.message : String(err)}`);
        }
        return null;
    }
}
export async function writeRunChatBinding(runId, binding, opts = {}) {
    const dir = await getOrEnsureRunDir(runId, opts);
    const path = join(dir, 'chat-binding.json');
    await atomicWriteFile(path, JSON.stringify(binding, null, 2));
    return path;
}
export async function readRunChatBinding(runId, opts = {}) {
    const dir = await getOrEnsureRunDir(runId, opts);
    const path = join(dir, 'chat-binding.json');
    const raw = await fs.readFile(path, 'utf-8');
    const parsed = JSON.parse(raw);
    if (!parsed.chatId || !parsed.larkAppId) {
        throw new Error(`Invalid workflow chat binding at ${path}`);
    }
    return { chatId: parsed.chatId, larkAppId: parsed.larkAppId };
}
async function getOrEnsureRunDir(runId, opts) {
    if (opts.runDir) {
        await fs.mkdir(opts.runDir, { recursive: true });
        return opts.runDir;
    }
    return ensureRunDir(runId, opts.runsDir);
}
//# sourceMappingURL=loader.js.map