import { promises as fs } from "fs";
import path from "path";

/**
 * The app registry — the single source of truth for which POCs exist and where
 * each is served. The directory admin edits it; the cards page renders it; and
 * (later) the routing/build can derive each app's BASE_PATH from its `path`.
 *
 * Stored as JSON at <cwd>/data/apps.json (override with REGISTRY_PATH). The `data`
 * dir is kept out of the CI's `rsync --delete`, so runtime edits survive deploys.
 * The file is seeded from DEFAULT_APPS on first run, so a fresh environment isn't
 * empty.
 */
export type AppStatus = "active" | "wip" | "offline";

export interface AppEntry {
  slug: string; // unique, kebab-case; also the default URL segment
  title: string;
  description: string;
  path: string; // where it's served, e.g. "/product-ai-automation" (or a full URL)
  status: AppStatus;
  roles: string[]; // empty = visible to everyone
}

const FILE = process.env.REGISTRY_PATH || path.join(process.cwd(), "data", "apps.json");

const DEFAULT_APPS: AppEntry[] = [
  {
    slug: "product-ai-automation",
    title: "AI Product Data Aggregation",
    description:
      "Enriches Ergonode PIM products via Google Vertex AI — research, image & video generation, quality scoring and write-back.",
    path: "/product-ai-automation",
    status: "active",
    roles: [],
  },
];

const SLUG_RE = /^[a-z][a-z0-9-]*$/;

export async function readApps(): Promise<AppEntry[]> {
  try {
    const data = JSON.parse(await fs.readFile(FILE, "utf8"));
    return Array.isArray(data?.apps) ? data.apps : [];
  } catch {
    // Missing/unreadable → seed from defaults (best-effort; ignore write failures).
    try {
      await writeApps(DEFAULT_APPS);
    } catch {
      /* read-only fs — fall through with the defaults in memory */
    }
    return DEFAULT_APPS;
  }
}

export async function writeApps(apps: AppEntry[]): Promise<void> {
  await fs.mkdir(path.dirname(FILE), { recursive: true });
  await fs.writeFile(FILE, JSON.stringify({ apps }, null, 2) + "\n", "utf8");
}

/** Validate + normalise one entry from the admin UI. Returns an error message or null. */
export function validateEntry(e: Partial<AppEntry>): string | null {
  if (!e.slug || !SLUG_RE.test(e.slug)) return "slug must be kebab-case (e.g. invoice-ocr)";
  if (!e.title?.trim()) return "title is required";
  if (!e.path || !(e.path.startsWith("/") || e.path.startsWith("http"))) {
    return 'path must start with "/" (a subpath) or "http" (a full URL)';
  }
  if (e.status && !["active", "wip", "offline"].includes(e.status)) return "invalid status";
  return null;
}

/** Add or replace an app by slug; persists. */
export async function upsertApp(entry: AppEntry): Promise<AppEntry[]> {
  const apps = await readApps();
  const next = apps.filter((a) => a.slug !== entry.slug);
  next.push({
    slug: entry.slug,
    title: entry.title.trim(),
    description: (entry.description || "").trim(),
    path: entry.path,
    status: entry.status || "active",
    roles: Array.isArray(entry.roles) ? entry.roles : [],
  });
  next.sort((a, b) => a.title.localeCompare(b.title));
  await writeApps(next);
  return next;
}

/** Remove an app by slug; persists. */
export async function removeApp(slug: string): Promise<AppEntry[]> {
  const apps = (await readApps()).filter((a) => a.slug !== slug);
  await writeApps(apps);
  return apps;
}
