/**
 * HostClaimStore — host/session claim files (WP-02).
 */

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

import { AtomicFile } from "../atomic/AtomicFile.ts";
import { SwitchboardPaths } from "../paths/SwitchboardPaths.ts";

export interface HostClaimRecord {
  schema: "codeflowmu.host-claim/v1";
  hostId: string;
  sessionKey: string;
  workerId?: string;
  runId?: string;
  acquiredAt: string;
  expiresAt?: string;
  metadata?: Record<string, string>;
}

export class HostClaimStore {
  readonly #paths: SwitchboardPaths;

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

  async acquire(claim: HostClaimRecord): Promise<string> {
    const path = this.#paths.claimFile(claim.hostId, claim.sessionKey);
    if (await AtomicFile.exists(path)) {
      const existing = await AtomicFile.readJson<HostClaimRecord>(path);
      if (
        existing.workerId &&
        claim.workerId &&
        existing.workerId !== claim.workerId
      ) {
        throw new Error(
          `claim conflict: ${claim.hostId}/${claim.sessionKey} held by ${existing.workerId}`,
        );
      }
    }
    await AtomicFile.writeJson(path, claim);
    return path;
  }

  async get(
    hostId: string,
    sessionKey: string,
  ): Promise<HostClaimRecord | null> {
    const path = this.#paths.claimFile(hostId, sessionKey);
    if (!(await AtomicFile.exists(path))) return null;
    return AtomicFile.readJson<HostClaimRecord>(path);
  }

  async release(hostId: string, sessionKey: string): Promise<boolean> {
    const path = this.#paths.claimFile(hostId, sessionKey);
    try {
      await fs.unlink(path);
      return true;
    } catch {
      return false;
    }
  }

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

  async recoverOrphans(hostId: string): Promise<string[]> {
    const result = await AtomicFile.recoverOrphanTmp(
      this.#paths.claimsDir(hostId),
    );
    return result.removed;
  }
}
