const width = 768;
const cacheImageDir = path.resolve(".clover/question_images");

// Cached browser session
const getBrowser = RefCountedExpirable(
  () =>
    puppeteer.launch({
      // headless: false,
      args: ["--no-sandbox", "--disable-setuid-sandbox"],
    }),
  (b) => b.close(),
);

export async function renderQuestionImage(question: Question) {
  const html = await renderViewToString("q+a/embed-image", { question });

  // this browser session will be reused if multiple images are generated
  // either at the same time or within a 5-minute time span. the dispose
  // symbol
  using sharedBrowser = await getBrowser();
  const b = sharedBrowser.value;

  const p = await b.newPage();
  await p.setViewport({ width, height: 400 });
  await p.setContent(html);
  try {
    await p.waitForNetworkIdle({ idleTime: 100, timeout: 500 });
  } catch (e) {}
  const height = await p.evaluate(() => {
    const e = document.querySelector("main")!;
    return e.getBoundingClientRect().height;
  });
  const buf = await p.screenshot({
    path: "screenshot.png",
    type: "png",
    captureBeyondViewport: true,
    clip: { x: 0, width, y: 0, height: height, scale: 1.5 },
  });
  await p.close();

  return Buffer.from(buf);
}

export async function getQuestionImage(
  question: Question,
  headOnly: boolean,
): Promise<Response> {
  const hash = crypto.createHash("sha1")
    .update(question.qmid + question.type + question.text)
    .digest("hex");

  const headers = {
    "Content-Type": "image/png",
    "Cache-Control": "public, max-age=31536000",
    "ETag": `"${hash}"`,
    "Last-Modified": question.date.toUTCString(),
  };

  if (headOnly) {
    return new Response(null, { headers });
  }

  const cachedFilePath = path.join(cacheImageDir, `/${hash}.png`);
  let buf: Buffer;
  try {
    buf = await fs.readFile(cachedFilePath);
  } catch (e: any) {
    if (e.code !== "ENOENT") throw e;
    buf = await renderQuestionImage(question);
    fs.writeMkdir(cachedFilePath, buf).catch(() => {});
  }

  return new Response(buf, { headers });
}

import * as crypto from "node:crypto";
import * as fs from "#sitegen/fs";
import * as path from "node:path";
import * as puppeteer from "puppeteer";
import { Question } from "@/q+a/models/Question.ts";
import { RefCountedExpirable } from "#sitegen/async";
import { renderViewToString } from "#sitegen/view";
