import { randomUUID } from 'node:crypto';
import { appendFile, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'node:path';
export const RUN_WORKSPACE_DIR = '.owlcoda-run';
export const RUN_WORKSPACE_STRUCTURE_VERSION = 2;
export const RUN_WORKSPACE_DIRECTORIES = ['stage', 'final', 'assets', 'scripts', 'evidence', 'notes'];
export function getRunWorkspacePaths(outputRoot, cwd = process.cwd()) {
    const resolvedOutputRoot = resolvePath(outputRoot, cwd);
    const runDir = join(resolvedOutputRoot, RUN_WORKSPACE_DIR);
    return buildRunWorkspacePaths(resolvedOutputRoot, runDir);
}
export async function createRunWorkspace(options) {
    const cwd = resolvePath(options.cwd ?? process.cwd(), process.cwd());
    const paths = getRunWorkspacePaths(options.outputRoot, cwd);
    const createdAt = options.createdAt ?? new Date().toISOString();
    const manifest = {
        version: 1,
        structureVersion: RUN_WORKSPACE_STRUCTURE_VERSION,
        runId: options.runId ?? `run-${randomUUID()}`,
        createdAt,
        outputRoot: paths.outputRoot,
        cwd,
        createdDirectories: [...RUN_WORKSPACE_DIRECTORIES],
        ...(options.taskFamily ? { taskFamily: options.taskFamily } : {}),
        ...(options.deliverableMode ? { deliverableMode: options.deliverableMode } : {}),
    };
    await mkdir(paths.outputRoot, { recursive: true });
    await mkdir(paths.runDir, { recursive: true });
    for (const directory of RUN_WORKSPACE_DIRECTORIES) {
        await mkdir(join(paths.outputRoot, directory), { recursive: true });
    }
    await writeJson(paths.manifestPath, manifest);
    await writeJson(paths.skillRoutePath, options.skillRoute ?? {});
    await writeJson(paths.planPath, options.plan ?? { steps: [] });
    await writeJson(paths.verificationPath, options.verification ?? { checks: [], results: [] });
    if (options.projectMap) {
        await writeJson(paths.projectMapPath, options.projectMap);
    }
    await writeJson(paths.checkpointPath, options.checkpoint ?? {
        version: 1,
        updatedAt: createdAt,
        status: 'initialized',
    });
    await writeJson(paths.artifactsPath, {
        version: 1,
        updatedAt: createdAt,
        artifacts: options.artifacts ?? [],
    });
    await writeFile(paths.eventsPath, '', { flag: 'a' });
    return { paths, manifest };
}
export async function readManifest(runRef, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    return readJson(paths.manifestPath);
}
export async function writeCheckpoint(runRef, checkpoint, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    await writeJson(paths.checkpointPath, checkpoint);
    return checkpoint;
}
export async function readCheckpoint(runRef, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    return readJson(paths.checkpointPath);
}
export async function writeSkillRoute(runRef, skillRoute, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    await writeJson(paths.skillRoutePath, skillRoute);
}
export async function writePlan(runRef, plan, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    await writeJson(paths.planPath, plan);
}
export async function writeVerification(runRef, verification, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    await writeJson(paths.verificationPath, verification);
}
export async function writeProjectMap(runRef, projectMap, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    await writeJson(paths.projectMapPath, projectMap);
    return projectMap;
}
export async function readProjectMap(runRef, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    return readJson(paths.projectMapPath);
}
export async function readArtifactLedger(runRef, options = {}, cwd = process.cwd()) {
    if (options.refresh) {
        return refreshArtifactLedger(runRef, cwd);
    }
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    return readJson(paths.artifactsPath);
}
export async function recordArtifact(runRef, options, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    const manifest = await readManifest(paths.runDir);
    const ledger = await readArtifactLedger(paths.runDir);
    const now = new Date().toISOString();
    const artifactPath = resolveArtifactPath(options.path, manifest.outputRoot);
    const observed = await observeArtifact(artifactPath);
    const existingIndex = ledger.artifacts.findIndex((artifact) => normalize(artifact.path) === normalize(artifactPath));
    const existing = existingIndex >= 0 ? ledger.artifacts[existingIndex] : undefined;
    const record = {
        id: options.id ?? existing?.id ?? `artifact-${ledger.artifacts.length + 1}`,
        path: artifactPath,
        origin: options.origin,
        size: observed.size,
        mtime: observed.mtime,
        mtimeMs: observed.mtimeMs,
        ...(options.stepId ? { stepId: options.stepId } : existing?.stepId ? { stepId: existing.stepId } : {}),
        participatesInFinal: options.participatesInFinal ?? existing?.participatesInFinal ?? true,
        status: observed.status,
        ...(options.sourcePath ? { sourcePath: options.sourcePath } : existing?.sourcePath ? { sourcePath: existing.sourcePath } : {}),
        recordedAt: existing?.recordedAt ?? now,
        updatedAt: now,
        ...(observed.status === 'missing' ? { missingAt: existing?.missingAt ?? now } : {}),
    };
    if (existingIndex >= 0) {
        ledger.artifacts[existingIndex] = record;
    }
    else {
        ledger.artifacts.push(record);
    }
    ledger.updatedAt = now;
    await writeJson(paths.artifactsPath, ledger);
    return record;
}
export async function refreshArtifactLedger(runRef, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    const ledger = await readArtifactLedger(paths.runDir);
    let changed = false;
    const now = new Date().toISOString();
    const artifacts = [];
    for (const artifact of ledger.artifacts) {
        const observed = await observeArtifact(artifact.path);
        const next = {
            ...artifact,
            size: observed.status === 'present' ? observed.size : artifact.size,
            mtime: observed.status === 'present' ? observed.mtime : artifact.mtime,
            mtimeMs: observed.status === 'present' ? observed.mtimeMs : artifact.mtimeMs,
            status: observed.status,
            updatedAt: artifact.updatedAt,
            ...(observed.status === 'missing'
                ? { missingAt: artifact.missingAt ?? now }
                : {}),
        };
        if (observed.status === 'present' && next.missingAt) {
            delete next.missingAt;
        }
        if (!artifactRecordsEqual(artifact, next)) {
            next.updatedAt = now;
            changed = true;
        }
        artifacts.push(next);
    }
    const refreshed = {
        version: ledger.version,
        updatedAt: changed ? now : ledger.updatedAt,
        artifacts,
    };
    if (changed) {
        await writeJson(paths.artifactsPath, refreshed);
    }
    return refreshed;
}
export async function recordEvent(runRef, event, cwd = process.cwd()) {
    const paths = getRunWorkspacePathsFromRef(runRef, cwd);
    const manifest = await readManifest(paths.runDir);
    const stored = {
        ...event,
        at: event.at ?? new Date().toISOString(),
        runId: manifest.runId,
    };
    await appendFile(paths.eventsPath, `${JSON.stringify(stored)}\n`, 'utf8');
    return stored;
}
export function getRunWorkspacePathsFromRef(runRef, cwd = process.cwd()) {
    const resolved = resolvePath(runRef, cwd);
    if (basename(resolved) === 'manifest.json') {
        return pathsFromRunDir(dirname(resolved));
    }
    if (basename(resolved) === RUN_WORKSPACE_DIR) {
        return pathsFromRunDir(resolved);
    }
    return getRunWorkspacePaths(resolved);
}
function pathsFromRunDir(runDir) {
    const outputRoot = dirname(runDir);
    return buildRunWorkspacePaths(outputRoot, runDir);
}
function buildRunWorkspacePaths(outputRoot, runDir) {
    return {
        outputRoot,
        runDir,
        manifestPath: join(runDir, 'manifest.json'),
        checkpointPath: join(runDir, 'checkpoint.json'),
        skillRoutePath: join(runDir, 'skill-route.json'),
        planPath: join(runDir, 'plan.json'),
        artifactsPath: join(runDir, 'artifacts.json'),
        verificationPath: join(runDir, 'verification.json'),
        projectMapPath: join(runDir, 'project-map.json'),
        eventsPath: join(runDir, 'events.jsonl'),
        stageDir: join(outputRoot, 'stage'),
        finalDir: join(outputRoot, 'final'),
        assetsDir: join(outputRoot, 'assets'),
        scriptsDir: join(outputRoot, 'scripts'),
        evidenceDir: join(outputRoot, 'evidence'),
        notesDir: join(outputRoot, 'notes'),
    };
}
function resolvePath(path, cwd) {
    return normalize(isAbsolute(path) ? resolve(path) : resolve(cwd, path));
}
function resolveArtifactPath(path, outputRoot) {
    return normalize(isAbsolute(path) ? resolve(path) : resolve(outputRoot, path));
}
async function observeArtifact(path) {
    try {
        const info = await stat(path);
        return {
            status: 'present',
            size: info.size,
            mtime: info.mtime.toISOString(),
            mtimeMs: info.mtimeMs,
        };
    }
    catch (err) {
        if (isMissingFileError(err)) {
            return {
                status: 'missing',
                size: null,
                mtime: null,
                mtimeMs: null,
            };
        }
        throw err;
    }
}
function isMissingFileError(err) {
    return typeof err === 'object'
        && err !== null
        && 'code' in err
        && err.code === 'ENOENT';
}
async function readJson(path) {
    const raw = await readFile(path, 'utf8');
    return JSON.parse(raw);
}
async function writeJson(path, value) {
    await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
function artifactRecordsEqual(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}
//# sourceMappingURL=run-workspace.js.map