/**
 * md2img - Markdown + Mermaid + LaTeX => PNG
 *
 * Usage:
 *   node render.js <input.md> [output.png] [--paper=a4|letter] [--dark|--light]
 *
 * Rendering strategy:
 *   - LaTeX (KaTeX): pre-rendered in Node.js → inline HTML
 *   - Mermaid: rendered in browser via bundled mermaid.js
 *   - Code highlighting: bundled highlight.js
 *
 * Browser: auto-detects puppeteer-core (local Edge/Chrome) or puppeteer (bundled Chromium)
 * Environment variable CHROME_PATH can override browser executable path
 */

const fs = require('fs');
const path = require('path');
const { marked } = require('marked');
const katex = require('katex');

const assetsDir = path.join(__dirname, 'assets');
const katexCss = fs.readFileSync(path.join(assetsDir, 'katex.min.css'), 'utf-8');
const githubCss = fs.readFileSync(path.join(assetsDir, 'github.min.css'), 'utf-8');
const githubDarkCss = fs.readFileSync(path.join(assetsDir, 'github-dark.min.css'), 'utf-8');
const mermaidJs = fs.readFileSync(path.join(assetsDir, 'mermaid.min.js'), 'utf-8');
const highlightJs = fs.readFileSync(path.join(assetsDir, 'highlight.min.js'), 'utf-8');

// CLI args
const args = process.argv.slice(2);
let inputPath = null;
let outputPath = null;
let paper = 'a4';
let forcedTheme = null;

for (const arg of args) {
  if (arg.startsWith('--paper=')) { paper = arg.split('=')[1]; }
  else if (arg === '--dark') { forcedTheme = 'dark'; }
  else if (arg === '--light') { forcedTheme = 'light'; }
  else if (!inputPath) { inputPath = arg; }
  else if (!outputPath) { outputPath = arg; }
}

if (!inputPath) {
  console.error('Usage: node render.js <input.md> [output.png] [--paper=a4|letter] [--dark|--light]');
  process.exit(1);
}

inputPath = path.resolve(inputPath);
if (!outputPath) { outputPath = inputPath.replace(/\.md$/i, '.png'); }
outputPath = path.resolve(outputPath);

const markdown = fs.readFileSync(inputPath, 'utf-8');

// Page dimensions
const PAGE_WIDTH = paper === 'letter' ? 816 : 794;
const MARGIN = 48;

// Helpers
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// LaTeX rendering helper
function renderMath(text, displayMode) {
  try {
    return katex.renderToString(text, {
      displayMode,
      throwOnError: false,
      strict: false,  // Allow more LaTeX commands
    });
  } catch (e) {
    return displayMode
      ? `<div class="math-block math-error">${escapeHtml(text)}</div>`
      : `<span class="math-error">${escapeHtml(text)}</span>`;
  }
}

// marked extension for inline math $...$
const mathExtension = {
  name: 'math',
  level: 'inline',
  start(src) {
    const idx = src.search(/(?<!\$)\$(?!\$)/);
    return idx === -1 ? undefined : idx;
  },
  tokenizer(src) {
    // Match $...$ but not $$ or currency patterns like $5.00
    const match = src.match(/^(?<!\$)\$(?!\$)([^\$\n]+?)\$(?!\$)/);
    if (match) {
      const text = match[1].trim();
      // Skip currency patterns (only digits with optional decimal)
      if (/^\d+[\.,]?\d*$/.test(text)) {
        return;
      }
      return { type: 'math', raw: match[0], text, displayMode: false };
    }
  },
  renderer(token) {
    return renderMath(token.text, token.displayMode);
  },
};

// marked extension for block math $$...$$
const blockMathExtension = {
  name: 'blockMath',
  level: 'block',
  start(src) {
    const idx = src.indexOf('$$');
    return idx === -1 ? undefined : idx;
  },
  tokenizer(src) {
    const match = src.match(/^\$\$([\s\S]+?)\$\$\n?/);
    if (match) {
      return { type: 'blockMath', raw: match[0], text: match[1].trim() };
    }
  },
  renderer(token) {
    return `<div class="math-block">${renderMath(token.text, true)}</div>`;
  },
};

// marked extension for inline math \(...\)
const bracketInlineMathExtension = {
  name: 'bracketInlineMath',
  level: 'inline',
  start(src) {
    const idx = src.indexOf('\\(');
    return idx === -1 ? undefined : idx;
  },
  tokenizer(src) {
    const match = src.match(/^\\\((.+?)\\\)/);
    if (match) {
      return { type: 'bracketInlineMath', raw: match[0], text: match[1].trim() };
    }
  },
  renderer(token) {
    return renderMath(token.text, false);
  },
};

// marked extension for \[...\] block math
const bracketMathExtension = {
  name: 'bracketMath',
  level: 'block',
  start(src) {
    const idx = src.indexOf('\\[');
    return idx === -1 ? undefined : idx;
  },
  tokenizer(src) {
    const match = src.match(/^\\\[([\s\S]+?)\\\]\n?/);
    if (match) {
      return { type: 'bracketMath', raw: match[0], text: match[1].trim() };
    }
  },
  renderer(token) {
    return `<div class="math-block">${renderMath(token.text, true)}</div>`;
  },
};

// marked extension for \begin{env}...\end{env} environments
const envMathExtension = {
  name: 'envMath',
  level: 'block',
  start(src) {
    const idx = src.indexOf('\\begin{');
    return idx === -1 ? undefined : idx;
  },
  tokenizer(src) {
    // Supported math environments
    const envs = [
      'equation', 'equation\\*',
      'align', 'align\\*', 'alignat', 'alignat\\*',
      'gather', 'gather\\*', 'multline', 'multline\\*',
      'aligned', 'gathered', 'split', 'alignedat', 'multlined',
      'matrix', 'pmatrix', 'bmatrix', 'Bmatrix', 'vmatrix', 'Vmatrix',
      'smallmatrix', 'smallmatrix\\*', 'psmallmatrix', 'bsmallmatrix', 'Bsmallmatrix', 'vsmallmatrix', 'Vsmallmatrix',
      'cases', 'rcases', 'numcases', 'subnumcases',
      'array', 'CD',
      'subarray', 'subsplit',
    ];
    const envPattern = envs.map(e => e.replace('\\*', '\\*')).join('|');
    const regex = new RegExp(`^\\\\begin\\{(${envPattern})\\}([\\s\\S]*?)\\\\end\\{\\1\\}`);
    const match = src.match(regex);
    if (match) {
      return { type: 'envMath', raw: match[0], env: match[1], text: match[2].trim() };
    }
  },
  renderer(token) {
    return `<div class="math-block">${renderMath(token.text, true)}</div>`;
  },
};

// LaTeX rendering function (post-processing for non-marked syntax)
function renderLatex(text) {
  // Clean up extra <br> tags around math-block divs (from marked's breaks: true)
  text = text.replace(/<br\s*\/?>\s*<div class="math-block">/gi, '<div class="math-block">');
  text = text.replace(/<\/div>\s*<br\s*\/?>/gi, '</div>');
  return text;
}

// Configure marked with math extensions
marked.use({
  extensions: [
    blockMathExtension,
    bracketMathExtension,
    bracketInlineMathExtension,
    envMathExtension,
    mathExtension,
  ],
  renderer: {
    // Handle code blocks
    code(code, infostring, escaped) {
      const lang = (infostring || '').trim();
      if (lang === 'mermaid') {
        return `<div class="mermaid">${code}</div>`;
      }
      const langClass = lang ? ` language-${escapeHtml(lang)}` : '';
      return `<pre><code class="hljs${langClass}">${escapeHtml(code)}</code></pre>`;
    },
    // Handle inline code
    codespan(text) {
      return `<code class="inline-code">${escapeHtml(text)}</code>`;
    },
    // Handle tables for better styling
    table(header, body) {
      return `<div class="table-wrapper"><table><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
    },
  },
});
marked.setOptions({ breaks: true, gfm: true });

// renderer already defined in extension

// Build HTML
function buildHtml(md) {
  let html = marked.parse(md);
  html = renderLatex(html);
  return html;
}

// CSS template
function buildCss(theme) {
  const isDark = theme === 'dark';
  // CJK font stack with emoji support
  const cjkFonts = "'Noto Sans SC', 'Noto Sans CJK SC', 'Source Han Sans SC', 'WenQuanYi Micro Hei', 'PingFang SC', 'Microsoft YaHei', sans-serif";
  const emojiFonts = "'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', 'Noto Emoji', 'Android Emoji', 'EmojiOne Color', sans-serif";
  
  return `
:root { color-scheme: ${isDark ? 'dark' : 'light'}; }
${isDark ? `
:root { --bg: #1e1e1e; --text: #d4d4d4; --code-bg: #2d2d2d; --border: #404040; }
pre, code { background: var(--code-bg); color: var(--text); }
a { color: #79c0ff; }
.math-block { background: #2d2d2d !important; }
tr:nth-child(even) { background: rgba(255,255,255,0.03); }
blockquote { background: rgba(255,255,255,0.04); }
` : `
:root { --bg: #ffffff; --text: #24292e; --code-bg: #f6f8fa; --border: #d0d7de; }
a { color: #0550ae; }
.math-block { background: #f6f8fa !important; }
tr:nth-child(even) { background: rgba(0,0,0,0.03); }
blockquote { background: rgba(0,0,0,0.04); }
`}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  width: ${PAGE_WIDTH}px;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', ${cjkFonts}, ${emojiFonts};
  font-size: 14px;
  line-height: 1.7;
  padding: ${MARGIN}px;
  word-break: break-word;
  overflow-wrap: break-word;
}
h1, h2, h3, h4, h5, h6 { margin: 1.2em 0 0.5em; font-weight: 600; line-height: 1.3; }
h1 { font-size: 1.8em; border-bottom: 2px solid var(--border); padding-bottom: 0.3em; }
h2 { font-size: 1.4em; border-bottom: 1px solid var(--border); padding-bottom: 0.2em; }
h3 { font-size: 1.15em; }
p { margin: 0.6em 0; }
ul, ol { margin: 0.6em 0; padding-left: 1.8em; }
li { margin: 0.2em 0; }
li > p { margin: 0.3em 0; }
pre { margin: 0.8em 0; padding: 12px 16px; border-radius: 6px; overflow-x: auto; font-size: 13px; background: var(--code-bg); border: 1px solid var(--border); }
code { font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Noto Sans Mono CJK SC', ${emojiFonts}, monospace; font-size: 0.9em; }
p code, li code { padding: 2px 6px; border-radius: 4px; background: var(--code-bg); border: 1px solid var(--border); }
table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
th, td { border: 1px solid var(--border); padding: 6px 12px; text-align: left; }
th { background: var(--code-bg); font-weight: 600; }
.table-wrapper { overflow-x: auto; margin: 0.8em 0; }
.table-wrapper table { margin: 0; }
.inline-code { font-family: inherit; background: none; padding: 0; border: none; }
blockquote { margin: 0.8em 0; padding: 8px 16px; border-left: 4px solid var(--border); }
hr { border: none; border-top: 1px solid var(--border); margin: 1em 0; }
a { text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; height: auto; }
.mermaid { margin: 1em 0; text-align: center; }
.mermaid svg { max-width: 100%; height: auto; }
.mermaid-error { color: #f85149; padding: 12px; border: 1px dashed #f85149; border-radius: 6px; margin: 0.5em 0; text-align: left; }
.mermaid-error pre { color: #f85149; white-space: pre-wrap; font-size: 12px; }
.math-block { display: block; text-align: center; margin: 1em 0; padding: 12px; border-radius: 6px; overflow-wrap: break-word; word-break: break-word; max-width: 100%; overflow-x: visible; }
.math-error { color: #f85149; font-family: monospace; }
/* KaTeX CJK support */
.katex { font-family: 'KaTeX_Main', 'Noto Sans SC', 'Noto Sans CJK SC', sans-serif; }
.katex .mord, .katex .mop, .katex .mbin, .katex .mrel, .katex .mopen, .katex .mclose, .katex .mpunct, .katex .minner { font-family: inherit; }
`;
}

// Browser detection
const osPlatform = process.platform;
const osArch = process.arch;

function findBrowserPath() {
  if (process.env.CHROME_PATH && fs.existsSync(process.env.CHROME_PATH)) {
    return process.env.CHROME_PATH;
  }
  const candidates = {
    win32: [
      'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
      'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
      'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
      'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
    ],
    darwin: [
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
      '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
      '/Applications/Chromium.app/Contents/MacOS/Chromium',
    ],
    linux: [
      '/usr/bin/google-chrome',
      '/usr/bin/chromium-browser',
      '/usr/bin/chromium',
      '/snap/bin/chromium',
    ],
  };
  for (const p of (candidates[osPlatform] || [])) {
    if (fs.existsSync(p)) return p;
  }
  return null;
}

async function getBrowser() {
  let browserModule, usingCore = false;
  try {
    browserModule = require('puppeteer-core');
    usingCore = true;
  } catch {
    try {
      browserModule = require('puppeteer');
    } catch {
      console.error('Error: Neither puppeteer-core nor puppeteer is installed.');
      console.error('Run: cd scripts && npm install puppeteer-core');
      process.exit(1);
    }
  }

  const browserPath = findBrowserPath();
  if (usingCore) {
    if (!browserPath) {
      console.error('Error: puppeteer-core requires a local Chrome/Edge/Chromium.');
      console.error('Set CHROME_PATH env var or install Chrome/Edge/Chromium.');
      process.exit(1);
    }
    console.log(`Using: puppeteer-core`);
    console.log(`Browser: ${browserPath}`);
    return browserModule.launch({
      headless: 'new',
      executablePath: browserPath,
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
  } else {
    console.log('Using: puppeteer (bundled Chromium)');
    const args = ['--no-sandbox', '--disable-setuid-sandbox'];
    if (osPlatform === 'linux') {
      args.push('--disable-dev-shm-usage', '--disable-gpu', '--no-zygote');
    }
    return browserModule.launch({ headless: 'new', args });
  }
}

// Main
async function main() {
  console.log(`Input:  ${inputPath}`);
  console.log(`Output: ${outputPath}`);
  console.log(`Paper:  ${paper.toUpperCase()}`);
  console.log(`Theme:  ${forcedTheme || 'auto'}`);
  console.log(`OS:     ${osPlatform} (${osArch})`);

  const bodyHtml = buildHtml(markdown);
  const theme = forcedTheme || 'light';
  const mermaidTheme = theme;

  const html = `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=${PAGE_WIDTH}">
<style>${katexCss}</style>
<style>${theme === 'dark' ? githubDarkCss : githubCss}</style>
<script>${mermaidJs}</script>
<script>${highlightJs}</script>
<style>${buildCss(theme)}</style>
</head>
<body>
${bodyHtml}
<script>
mermaid.initialize({
  startOnLoad: true,
  theme: '${mermaidTheme}',
  securityLevel: 'loose',
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", "Noto Sans SC", "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Micro Hei", "PingFang SC", "Microsoft YaHei", sans-serif',
  errorNodes: {
    onError: function(error, node) {
      node.classList.add('mermaid-error');
      node.innerHTML = '<pre>' + error.message + '</pre>';
    }
  }
});
document.addEventListener('DOMContentLoaded', function() {
  // Highlight code blocks with error handling
  document.querySelectorAll('pre code:not(.hljs)').forEach(function(block) {
    hljs.highlightElement(block);
  });
});
</script>
</body>
</html>`;

  const browser = await getBrowser();

  try {
    const page = await browser.newPage();
    await page.setViewport({ width: PAGE_WIDTH, height: 800, deviceScaleFactor: 2 });

    await page.setContent(html, { waitUntil: 'networkidle2', timeout: 30000 });

    // Wait for mermaid to finish rendering
    await page.waitForFunction(() => {
      const mermaidDivs = document.querySelectorAll('.mermaid');
      if (mermaidDivs.length === 0) return true;
      return Array.from(mermaidDivs).every(div => div.getAttribute('data-processed') === 'true' || div.querySelector('svg'));
    }, { timeout: 30000 });

    // Extra wait for font rendering
    await sleep(1500);

    const bodyHeight = await page.evaluate(() => document.body.scrollHeight);

    await page.setViewport({
      width: PAGE_WIDTH,
      height: Math.max(bodyHeight, 200),
      deviceScaleFactor: 2,
    });

    await page.screenshot({
      path: outputPath,
      fullPage: true,
      type: 'png',
    });

    console.log(`Done: ${outputPath}`);
    const stats = fs.statSync(outputPath);
    console.log(`Size: ${(stats.size / 1024).toFixed(1)} KB`);
    console.log(`Dimensions: ${PAGE_WIDTH} x ${bodyHeight} px (2x scale)`);
  } finally {
    await browser.close();
  }
}

main().catch(err => {
  console.error('Error:', err.message || err);
  process.exit(1);
});
