/**
 * Capture blog-post screenshots of the product's Models page (the free-model rows).
 *
 * A deliberately lean sibling of capture-shots.mjs: that script drives a full scripted
 * LLM conversation for the landing hero shots, while the Models page needs no LLM at
 * all — a fresh user's default Project already presets the entire built-in model
 * catalog, so the free rows (Ling 3.0 Flash / Nemotron 3 Ultra / Laguna M.1 /
 * openrouter/free) render their light-yellow "Free" badges straight from the $0
 * pricing. Flow: boot the Web server against a temp data root serving the built web
 * dist -> provision one demo user per UI language (password rotated once so the
 * initial-password banner never shows) -> open /models, scroll the OpenRouter group
 * to the top of the viewport (Claude Opus 5 sits on its first card row, the free
 * rows a few rows below) -> screenshot, light theme, zh + en, as
 * free-models-page-<lang>-light.webp (re-encoded to WebP inside Chromium, same as
 * capture-shots.mjs).
 *
 * Output is a two-step flow, because blog images are not committed to this repo:
 *   1. this script writes into the gitignored staging dir packages/landing/.blog-assets/;
 *   2. upload the files it produced to the `blog-assets/` directory of the sibling
 *      `Prism-Shadow/penguin-harness-community` repo, which is what the posts load from
 *      (the renderer resolves /blog-assets/<name> there — see src/lib/links.ts).
 *
 * Prereqs: `pnpm --filter @prismshadow/penguin-{skills,core,server,web} build` and
 * Playwright's chromium. Run: `node scripts/capture-blog-shots.mjs` (or
 * `pnpm --filter @prismshadow/penguin-landing blog-shots`).
 */
import { spawn } from "node:child_process";
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "@playwright/test";

const HERE = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(HERE, "../../..");
// Gitignored staging dir: these images are hosted in the community repo, not committed here.
const OUT_DIR = path.resolve(HERE, "../.blog-assets");
const SRV_PORT = 8944; // Distinct from capture-shots.mjs (8940/8941) so both can run.
// Pins the otherwise-random seeded admin password (PENGUIN_SEED_ADMIN_PASSWORD below).
const ADMIN_PASSWORD = "penguin-0000";
// On loopback binds the App is canonically served on `localhost`; the 127.0.0.1
// counterpart is the Workspace-preview host, where /api deliberately answers 401
// (see server app.ts's canonical-host guard).
const BASE = `http://localhost:${SRV_PORT}`;

// ---------------------------------------------------------------------------
// Server + API helpers (same shape as capture-shots.mjs).
// ---------------------------------------------------------------------------

async function waitFor(url, tries = 60) {
  for (let i = 0; i < tries; i++) {
    try {
      const res = await fetch(url);
      if (res.ok) return;
    } catch {}
    await new Promise((r) => setTimeout(r, 500));
  }
  throw new Error(`server not ready: ${url}`);
}

async function api(cookie, method, url, body) {
  const res = await fetch(`${BASE}${url}`, {
    method,
    headers: {
      "content-type": "application/json",
      ...(cookie ? { cookie } : {}),
    },
    ...(body ? { body: JSON.stringify(body) } : {}),
  });
  if (!res.ok) throw new Error(`${method} ${url} -> ${res.status} ${await res.text()}`);
  return { json: await res.json().catch(() => ({})), setCookie: res.headers.get("set-cookie") };
}

async function login(userId, password) {
  const { setCookie } = await api(null, "POST", "/api/auth/login", { userId, password });
  if (!setCookie) throw new Error("no session cookie from login");
  return setCookie.split(";")[0];
}

/**
 * Per-language demo user (same ids as capture-shots.mjs). Unlike that script it does NOT
 * touch the model table: the fresh Project's preset catalog is exactly what the shot is
 * about. The password is rotated once so the initial-password banner stays out of frame.
 */
async function provisionUser(adminCookie, lang) {
  const userId = lang === "zh" ? "demo" : "alex";
  const initial = `${userId}12345`;
  await api(adminCookie, "POST", "/api/admin/users", { userId, password: initial }).catch((e) => {
    if (!String(e).includes("409")) throw e;
  });
  let password = initial;
  const cookie = await login(userId, initial);
  try {
    await api(cookie, "PUT", "/api/me/password", {
      oldPassword: initial,
      newPassword: `penguin-${userId}-2026`,
    });
    password = `penguin-${userId}-2026`;
  } catch {}
  return { userId, password };
}

// ---------------------------------------------------------------------------
// Main.
// ---------------------------------------------------------------------------

const dataRoot = mkdtempSync(path.join(os.tmpdir(), "penguin-blog-shots-"));
mkdirSync(OUT_DIR, { recursive: true });

const srv = spawn("node", [path.join(ROOT, "packages/server/dist/index.js")], {
  env: {
    ...process.env,
    PENGUIN_HOME: path.join(dataRoot, "home"),
    PENGUIN_WEB_DB: path.join(dataRoot, "web.db"),
    PENGUIN_WEB_DIST: path.join(ROOT, "packages/web/dist"),
    PORT: String(SRV_PORT),
    HOST: "127.0.0.1",
    PENGUIN_SEED_ADMIN_PASSWORD: ADMIN_PASSWORD,
  },
  stdio: ["ignore", "pipe", "pipe"],
});
srv.stderr.on("data", (d) => process.stderr.write(`[srv!] ${d}`));
process.on("exit", () => {
  try {
    srv.kill();
  } catch {}
});

try {
  await waitFor(`${BASE}/`);
  console.log(`[blog-shots] server ready on ${BASE}`);

  const adminCookie = await login("admin", ADMIN_PASSWORD);
  const browser = await chromium.launch();

  // WebP encoder: Chromium re-encodes the PNG screenshot buffer via canvas (same
  // convention and quality as capture-shots.mjs, keeping repo assets small).
  const encoderPage = await browser.newPage();
  async function saveWebp(pngBuffer, fileName) {
    const dataUrl = await encoderPage.evaluate(async (b64) => {
      const img = new Image();
      img.src = `data:image/png;base64,${b64}`;
      await img.decode();
      const canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      canvas.getContext("2d").drawImage(img, 0, 0);
      return canvas.toDataURL("image/webp", 0.82);
    }, pngBuffer.toString("base64"));
    writeFileSync(path.join(OUT_DIR, fileName), Buffer.from(dataUrl.split(",")[1], "base64"));
    console.log(`[blog-shots] ${fileName}`);
  }

  for (const lang of ["zh", "en"]) {
    const user = await provisionUser(adminCookie, lang);

    // 1280x900 @1.5x, light theme (the blog embeds a single light variant per language,
    // like rag-app-<lang>-light.webp): tall enough that the OpenRouter group shows Claude
    // Opus 5 (first card row) together with the free rows and their badges.
    const context = await browser.newContext({
      viewport: { width: 1280, height: 900 },
      deviceScaleFactor: 1.5,
      locale: lang === "zh" ? "zh-CN" : "en-US",
    });
    await context.addInitScript(
      ([t, l]) => {
        localStorage.setItem("penguin.theme", t);
        localStorage.setItem("penguin.lang", l);
      },
      ["light", lang],
    );
    const page = await context.newPage();
    await page.goto(`${BASE}/login`);
    const loginRes = await page.request.post(`${BASE}/api/auth/login`, {
      data: { userId: user.userId, password: user.password },
    });
    if (!loginRes.ok()) throw new Error(`browser login failed: ${loginRes.status()}`);

    await page.goto(`${BASE}/models`);
    // The catalog is loaded once a free row's card is rendered (its "Free" badge with it).
    const lingCard = page.getByText("Ling 3.0 Flash (free)").first();
    await lingCard.waitFor({ timeout: 20000 });
    // Scroll the OpenRouter group section to the top of the viewport so the group header,
    // Claude Opus 5 (first card row) and the badged free rows all sit in frame.
    await page
      .getByRole("button", { name: /OpenRouter/ })
      .first()
      .evaluate((el) =>
        el.closest("section").scrollIntoView({ block: "start", behavior: "instant" }),
      );
    await page.waitForTimeout(1200);
    await saveWebp(await page.screenshot(), `free-models-page-${lang}-light.webp`);

    await context.close();
  }

  await browser.close();
  console.log(`[blog-shots] done -> ${OUT_DIR}`);
  process.exit(0);
} catch (err) {
  console.error("[blog-shots] FAILED:", err);
  process.exit(1);
}
