/**
 * PDF Generator — converts markdown or structured sections into a styled PDF.
 *
 * Uses Playwright with a local Chromium-family browser to render HTML → PDF.
 * Falls back to a styled HTML file if browser PDF rendering is unavailable.
 */

import * as fs from "fs";
import { execFileSync } from "node:child_process";

interface PDFSection {
  heading?: string;
  content: string;
}

interface PDFOptions {
  title?: string;
  author?: string;
  sections?: PDFSection[];
  markdown?: string;
  format?: "A4" | "Letter";
  landscape?: boolean;
}

function which(command: string): string | undefined {
  try {
    const output = execFileSync("which", [command], { encoding: "utf-8" }).trim();
    return output || undefined;
  } catch {
    return undefined;
  }
}

function resolveBrowserExecutable(): string | undefined {
  const envCandidates = [
    process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
    process.env.CHROME_PATH,
    process.env.BRAVE_PATH,
    process.env.PUPPETEER_EXECUTABLE_PATH,
  ]
    .map((value) => value?.trim())
    .filter((value): value is string => Boolean(value));

  const platformCandidates =
    process.platform === "darwin"
      ? [
          "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
          "/Applications/Chromium.app/Contents/MacOS/Chromium",
          "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
          "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
        ]
      : process.platform === "win32"
        ? [
            process.env.LOCALAPPDATA
              ? `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`
              : "",
            process.env.LOCALAPPDATA
              ? `${process.env.LOCALAPPDATA}\\Chromium\\Application\\chrome.exe`
              : "",
            "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
            "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
            "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
            "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
            "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
            "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
          ]
        : [
            "/usr/bin/google-chrome",
            "/usr/bin/google-chrome-stable",
            "/usr/bin/chromium",
            "/usr/bin/chromium-browser",
            "/usr/bin/microsoft-edge",
            "/usr/bin/brave-browser",
            "/snap/bin/chromium",
            "/snap/bin/brave",
          ];

  const discovered =
    process.platform === "win32"
      ? []
      : [
          which("google-chrome"),
          which("google-chrome-stable"),
          which("chromium"),
          which("chromium-browser"),
          which("microsoft-edge"),
          which("brave-browser"),
        ].filter((value): value is string => Boolean(value));

  for (const candidate of [...envCandidates, ...platformCandidates, ...discovered]) {
    if (candidate && fs.existsSync(candidate)) {
      return candidate;
    }
  }

  return undefined;
}

/**
 * Render markdown/sections to a styled HTML string, then use Playwright to
 * produce a PDF file.
 */
export async function generatePDF(
  outputPath: string,
  options: PDFOptions,
): Promise<{ success: boolean; path: string; size: number }> {
  const html = buildHTML(options);

  // Try Playwright first (available if browser tools are installed)
  try {
    const playwrightModule = (await import("playwright")) as Any;
    const chromium =
      playwrightModule.chromium ||
      playwrightModule.default?.chromium ||
      playwrightModule.playwright?.chromium;
    const executablePath = resolveBrowserExecutable();

    if (chromium && executablePath) {
      const browser = await chromium.launch({
        headless: true,
        executablePath,
        args: ["--no-sandbox", "--disable-setuid-sandbox"],
      });
      try {
        const page = await browser.newPage();
        await page.setContent(html, { waitUntil: "networkidle" });
        await page.emulateMedia({ media: "screen" });
        await page.pdf({
          path: outputPath,
          format: options.format || "A4",
          landscape: options.landscape || false,
          printBackground: true,
          margin: { top: "1cm", right: "1.5cm", bottom: "1cm", left: "1.5cm" },
        });
      } finally {
        await browser.close();
      }

      const stat = fs.statSync(outputPath);
      return { success: true, path: outputPath, size: stat.size };
    }
  } catch {
    // Playwright or a local browser is unavailable; fall through to HTML file.
  }

  // Fallback: write styled HTML (can be opened in any browser and printed to PDF)
  const htmlPath = outputPath.replace(/\.pdf$/i, ".html");
  const finalPath = htmlPath === outputPath ? `${outputPath}.html` : htmlPath;
  fs.writeFileSync(finalPath, html, "utf-8");
  const stat = fs.statSync(finalPath);
  return { success: true, path: finalPath, size: stat.size };
}

function buildHTML(options: PDFOptions): string {
  let body = "";

  if (options.title) {
    body += `<h1 class="doc-title">${escapeHtml(options.title)}</h1>\n`;
  }

  if (options.markdown) {
    // Convert basic markdown to HTML
    body += markdownToHtml(options.markdown);
  }

  if (options.sections) {
    for (const section of options.sections) {
      if (section.heading) {
        body += `<h2>${escapeHtml(section.heading)}</h2>\n`;
      }
      body += markdownToHtml(section.content);
    }
  }

  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>${escapeHtml(options.title || "Document")}</title>
  <style>
    :root { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
    body { max-width: 720px; margin: 40px auto; padding: 0 20px; color: #1a1a1a; line-height: 1.6; font-size: 14px; }
    .doc-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; border-bottom: 2px solid #2563eb; padding-bottom: 8px; }
    h2 { font-size: 20px; margin-top: 24px; color: #1e40af; }
    h3 { font-size: 16px; margin-top: 18px; }
    p { margin: 8px 0; }
    ul, ol { padding-left: 24px; }
    li { margin: 4px 0; }
    code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
    pre { background: #f3f4f6; padding: 12px 16px; border-radius: 8px; overflow-x: auto; }
    pre code { background: none; padding: 0; }
    table { border-collapse: collapse; width: 100%; margin: 12px 0; }
    th, td { border: 1px solid #d1d5db; padding: 8px 12px; text-align: left; font-size: 13px; }
    th { background: #f9fafb; font-weight: 600; }
    blockquote { border-left: 4px solid #2563eb; margin: 12px 0; padding: 8px 16px; background: #eff6ff; }
    hr { border: none; border-top: 1px solid #e5e7eb; margin: 20px 0; }
    .meta { font-size: 12px; color: #6b7280; margin-bottom: 20px; }
  </style>
</head>
<body>
  ${options.author ? `<div class="meta">By ${escapeHtml(options.author)} &middot; ${new Date().toLocaleDateString()}</div>` : ""}
  ${body}
</body>
</html>`;
}

/** Minimal markdown → HTML converter for common patterns. */
function markdownToHtml(md: string): string {
  let html = escapeHtml(md);

  // Code blocks
  html = html.replace(
    /```(\w*)\n([\s\S]*?)```/g,
    (_m, _lang, code) => `<pre><code>${code.trim()}</code></pre>`,
  );
  // Inline code
  html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
  // Headers
  html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>");
  html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>");
  html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>");
  // Bold / italic
  html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
  html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
  // Unordered list
  html = html.replace(/^- (.+)$/gm, "<li>$1</li>");
  html = html.replace(/(<li>.*<\/li>\n?)+/g, (m) => `<ul>${m}</ul>`);
  // Ordered list
  html = html.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
  // Blockquote
  html = html.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
  // Horizontal rule
  html = html.replace(/^---$/gm, "<hr>");
  // Links
  html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
  // Paragraphs (lines not already wrapped)
  html = html.replace(/^(?!<[hupob]|<li|<hr|<block)(.+)$/gm, "<p>$1</p>");

  return html;
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}
