import { defineConfig, loadEnv, type Plugin } from "vite";
import { fileURLToPath } from "node:url";
import fs from "node:fs";
import path from "node:path";
import { buildGhostBlog } from "./src/ghost/build";
import { renderEmptyState } from "./src/ghost/render";

const root = path.dirname(fileURLToPath(import.meta.url));

/** Marker in blog/index.html where Ghost cards are injected. */
const CARDS_MARKER = "<!-- GHOST_CARDS -->";

/**
 * Every blog/<slug>/index.html — the hand-authored posts plus the pages just
 * generated from Ghost. Globbed rather than listed so new posts need no config
 * change.
 */
function blogPageInputs(): Record<string, string> {
  const blogDir = path.resolve(root, "blog");
  const inputs: Record<string, string> = {};

  for (const entry of fs.readdirSync(blogDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue;
    const file = path.join(blogDir, entry.name, "index.html");
    if (fs.existsSync(file)) inputs[`blog-${entry.name}`] = file;
  }
  return inputs;
}

/** Injects the rendered Ghost cards into the blog listing grid. */
function ghostListingPlugin(cardsHtml: string): Plugin {
  return {
    name: "ghost-listing",
    transformIndexHtml: {
      order: "pre",
      handler(html, ctx) {
        if (!ctx.filename.replace(/\\/g, "/").endsWith("/blog/index.html")) return html;
        if (!html.includes(CARDS_MARKER)) {
          console.warn(`[ghost] ${CARDS_MARKER} not found in blog/index.html; cards not injected.`);
          return html;
        }
        // Empty when Ghost has no tagged posts, or (in dev) when it was
        // unreachable. The grid has no static cards behind it, so show the
        // empty state rather than a blank gap.
        return html.replace(CARDS_MARKER, cardsHtml || renderEmptyState());
      },
    },
  };
}

export default defineConfig(async ({ mode, command }) => {
  // Third arg "" loads unprefixed vars too: these are read here in Node at build
  // time and never inlined into the client bundle, so the Ghost key stays server-side.
  const env = { ...process.env, ...loadEnv(mode, root, "") };

  let cardsHtml = "";
  try {
    // Always fetch fresh, so a newly published post shows up on the next dev
    // restart or deploy. In dev only, fall back to the last cached response if
    // Ghost is unreachable.
    ({ cardsHtml } = await buildGhostBlog(env, { allowStaleFallback: command === "serve" }));
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (command === "build") {
      // Fail the build rather than publish a blog with posts silently missing.
      // On Vercel a failed build leaves the previous deployment serving.
      throw new Error(`[ghost] blog generation failed: ${message}`);
    }
    // In dev, keep the site usable — the hand-authored posts still render.
    console.warn(`[ghost] ${message}\n[ghost] Continuing without Ghost posts (dev only).`);
  }

  return {
    base: "./",
    server: { port: 4190 },
    plugins: [ghostListingPlugin(cardsHtml)],
    build: {
      rollupOptions: {
        input: {
          main: path.resolve(root, "index.html"),
          faq: path.resolve(root, "faq.html"),
          blog: path.resolve(root, "blog/index.html"),
          terms: path.resolve(root, "terms/index.html"),
          ...blogPageInputs(),
        },
        output: {
          manualChunks: { three: ["three"], gsap: ["gsap"] },
        },
      },
    },
  };
});
