import mysql from "mysql2/promise";
import fallback from "../config/projects.json";

/**
 * Projects, read from cja_projects at BUILD time.
 *
 * The site is static, so this runs once during `npm run build` and every
 * project page ships as real HTML — crawlable, instant, no client-side fetch
 * and no loading state. The cost is that content changes need a rebuild, which
 * is exactly what the agent pipeline already does.
 *
 * If the database is unreachable the build does NOT fail: it falls back to
 * config/projects.json and logs loudly. A dead database should never be able
 * to take the whole site down with it.
 */

const env = import.meta.env;

let cache = null;

const parseJson = (value, whenEmpty) => {
  if (!value) return whenEmpty;
  if (typeof value === "object") return value; // mysql2 may pre-parse JSON columns
  try {
    return JSON.parse(value);
  } catch {
    return whenEmpty;
  }
};

/** Shape a DB row into what the templates expect. */
const toProject = (row) => ({
  slug: row.slug,
  title: row.title,
  kind: row.kind,
  period: row.period_label,
  status: row.status,
  summary: row.summary,
  lede: row.lede,
  body: row.body,
  role: row.role,
  categories: parseJson(row.categories, []),
  stack: parseJson(row.stack, []),
  skills: parseJson(row.skills, []),
  metrics: parseJson(row.metrics, []),
  gallery: parseJson(row.gallery, []),
  links: parseJson(row.links, {}),
  cover: row.cover_image || null,
  coverAlt: row.cover_alt || "",
  seoTitle: row.seo_title || null,
  seoDescription: row.seo_description || null,
  featured: Boolean(row.featured),
});

export async function getProjects() {
  if (cache) return cache;

  const host = env.DB_HOST;
  const database = env.DB_NAME;
  const user = env.DB_USER;
  const password = env.DB_PASS;

  if (!host || !database || !user) {
    console.warn(
      "[projects] No database credentials in env — falling back to config/projects.json",
    );
    cache = fallback;
    return cache;
  }

  let conn;
  try {
    conn = await mysql.createConnection({ host, user, password, database });
    const [rows] = await conn.execute(
      `SELECT * FROM cja_projects
        WHERE published = 1
        ORDER BY sort_order ASC, started_on DESC`,
    );

    if (!rows.length) {
      console.warn("[projects] cja_projects returned no published rows — using fallback");
      cache = fallback;
    } else {
      cache = rows.map(toProject);
      console.log(`[projects] loaded ${cache.length} from cja_projects`);
    }
  } catch (error) {
    // Loud, but not fatal. A build that silently ships stale content is worse
    // than one that tells you the database was unreachable.
    console.error(`[projects] DB read failed (${error.message}) — using fallback`);
    cache = fallback;
  } finally {
    await conn?.end();
  }

  return cache;
}

export async function getProject(slug) {
  return (await getProjects()).find((p) => p.slug === slug) ?? null;
}

/** Category names with counts, derived from the projects themselves. */
export async function getCategories() {
  const projects = await getProjects();
  const counts = {};
  for (const p of projects) {
    for (const c of p.categories ?? []) counts[c] = (counts[c] ?? 0) + 1;
  }
  return Object.entries(counts)
    .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
    .map(([name, count]) => ({ name, count, slug: name.toLowerCase() }));
}
