import fs from 'node:fs';
import path from 'node:path';

/**
 * @typedef {object} PrStateEntry
 * @property {string} pr_url
 * @property {number|null} pr_number
 * @property {string|null} pr_repo
 * @property {string|null} pr_created_at
 * @property {'open'|'merged'|'merged_destroyed'} pr_state
 * @property {string|null} pr_merged_at
 * @property {boolean} auto_destroy_on_merge
 */

/**
 * @param {string} filePath
 */
export function createWorldPrStateStore(filePath) {
  /** @type {Record<string, PrStateEntry>} */
  let cache = {};
  let cacheMtimeMs = -1;

  function readFromDisk() {
    if (!fs.existsSync(filePath)) {
      cache = {};
      cacheMtimeMs = 0;
      return;
    }
    try {
      const stat = fs.statSync(filePath);
      if (stat.mtimeMs === cacheMtimeMs) return;
      const raw = fs.readFileSync(filePath, 'utf-8');
      const parsed = JSON.parse(raw);
      cache = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
      cacheMtimeMs = stat.mtimeMs;
    } catch (err) {
      console.error(`world-pr-state: failed to read ${filePath}: ${err.message}`);
      cache = {};
      cacheMtimeMs = 0;
    }
  }

  function writeToDisk() {
    const dir = path.dirname(filePath);
    fs.mkdirSync(dir, { recursive: true });
    const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
    fs.writeFileSync(tmp, JSON.stringify(cache, null, 2), 'utf-8');
    fs.renameSync(tmp, filePath);
    try {
      cacheMtimeMs = fs.statSync(filePath).mtimeMs;
    } catch {
      cacheMtimeMs = 0;
    }
  }

  function getAll() {
    readFromDisk();
    return { ...cache };
  }

  /** @param {string} worldId */
  function get(worldId) {
    readFromDisk();
    return cache[worldId] ?? null;
  }

  /**
   * Upsert — merges data with the existing entry.
   * @param {string} worldId
   * @param {Partial<PrStateEntry>} data
   */
  function set(worldId, data) {
    readFromDisk();
    const existing = cache[worldId] ?? {};
    cache = { ...cache, [worldId]: { ...existing, ...data } };
    writeToDisk();
  }

  /** @param {string} worldId */
  function remove(worldId) {
    readFromDisk();
    if (!(worldId in cache)) return;
    const next = { ...cache };
    delete next[worldId];
    cache = next;
    writeToDisk();
  }

  function getWorldsToWatch() {
    readFromDisk();
    return Object.entries(cache)
      .filter(([, entry]) => entry.pr_url && entry.pr_state !== 'merged_destroyed')
      .map(([worldId, entry]) => ({ worldId, ...entry }));
  }

  return { getAll, get, set, remove, getWorldsToWatch };
}
