/**
 * RunStore — per-worker run records as JSON (WP-02).
 */

import { promises as fs } from "node:fs";
import { join } from "node:path";

import { AtomicFile } from "../atomic/AtomicFile.ts";
import type { RunRecord } from "../contracts/RunRecord.ts";
import { SwitchboardPaths, type RunBucket } from "../paths/SwitchboardPaths.ts";

function bucketForState(state: RunRecord["state"]): RunBucket {
  if (
    state === "completed" ||
    state === "failed" ||
    state === "cancelled" ||
    state === "delivery_unknown"
  ) {
    return "settled";
  }
  return "active";
}

export class RunStore {
  readonly #paths: SwitchboardPaths;

  constructor(paths: SwitchboardPaths) {
    this.#paths = paths;
  }

  async put(record: RunRecord): Promise<string> {
    const bucket = bucketForState(record.state);
    const path = this.#paths.runFile(record.workerId, bucket, record.runId);
    // If moving between buckets, remove the other copy after write.
    const other: RunBucket = bucket === "active" ? "settled" : "active";
    const otherPath = this.#paths.runFile(record.workerId, other, record.runId);
    await AtomicFile.writeJson(path, record);
    if (await AtomicFile.exists(otherPath)) {
      await fs.unlink(otherPath).catch(() => undefined);
    }
    return path;
  }

  async get(
    workerId: string,
    runId: string,
  ): Promise<{ record: RunRecord; bucket: RunBucket; path: string } | null> {
    for (const bucket of ["active", "settled"] as RunBucket[]) {
      const path = this.#paths.runFile(workerId, bucket, runId);
      if (await AtomicFile.exists(path)) {
        return {
          record: await AtomicFile.readJson<RunRecord>(path),
          bucket,
          path,
        };
      }
    }
    return null;
  }

  async list(workerId: string, bucket: RunBucket): Promise<RunRecord[]> {
    const dir = this.#paths.runsDir(workerId, bucket);
    let names: string[] = [];
    try {
      names = await fs.readdir(dir);
    } catch {
      return [];
    }
    const out: RunRecord[] = [];
    for (const name of names.sort()) {
      if (!name.endsWith(".json") || name.includes(".tmp")) continue;
      out.push(await AtomicFile.readJson<RunRecord>(join(dir, name)));
    }
    return out;
  }

  async recoverOrphans(workerId: string): Promise<string[]> {
    const removed: string[] = [];
    for (const bucket of ["active", "settled"] as RunBucket[]) {
      const result = await AtomicFile.recoverOrphanTmp(
        this.#paths.runsDir(workerId, bucket),
      );
      removed.push(...result.removed.map((n) => `${bucket}/${n}`));
    }
    return removed;
  }
}
