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

import { atomicWriteUtf8, canonicalJson } from "./atomic.js";
import {
  emptyReviewDraft,
  REVIEW_DRAFT_RELATIVE_PATH,
  reviewDraftSchema,
  type ReviewDraft,
} from "./types.js";

export class CorruptReviewDraftError extends Error {
  readonly code = "corrupt-review-draft" as const;

  constructor(message: string) {
    super(message);
    this.name = "CorruptReviewDraftError";
  }
}

export function reviewDraftPath(reportDir: string): string {
  return join(reportDir, REVIEW_DRAFT_RELATIVE_PATH);
}

export async function loadReviewDraft(reportDir: string): Promise<ReviewDraft> {
  const path = reviewDraftPath(reportDir);
  let raw: string;
  try {
    raw = await readFile(path, "utf8");
  } catch (error) {
    const errno = (error as NodeJS.ErrnoException).code;
    if (errno === "ENOENT") {
      return emptyReviewDraft();
    }
    throw error;
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(raw) as unknown;
  } catch {
    throw new CorruptReviewDraftError("review draft is not valid JSON");
  }

  const result = reviewDraftSchema.safeParse(parsed);
  if (!result.success) {
    throw new CorruptReviewDraftError("review draft failed schema validation");
  }
  return result.data;
}

export async function saveReviewDraft(reportDir: string, draft: ReviewDraft): Promise<void> {
  const parsed = reviewDraftSchema.parse(draft);
  const path = reviewDraftPath(reportDir);
  await atomicWriteUtf8(path, `${canonicalJson(parsed)}\n`);
}

export async function withReviewDraft(
  reportDir: string,
  update: (draft: ReviewDraft) => ReviewDraft | Promise<ReviewDraft>,
): Promise<ReviewDraft> {
  const current = await loadReviewDraft(reportDir);
  const next = await update(current);
  const saved = reviewDraftSchema.parse({
    ...next,
    revision: current.revision + 1,
  });
  await saveReviewDraft(reportDir, saved);
  return saved;
}
