import { micromark } from 'micromark';
import { gfm, gfmHtml } from 'micromark-extension-gfm';
import { getDesignStyle } from './agent-config.js';
import { getBrandProfile } from './brand.js';

const EMOJI_RANGES = /[\u{1F000}-\u{1FFFF}\u{2300}-\u{23FF}\u{2460}-\u{24FF}\u{25A0}-\u{27BF}\u{2900}-\u{297F}\u{2B00}-\u{2BFF}\u{3000}-\u{303F}\u{1F1E6}-\u{1F1FF}]/gu;
const VARIATION_SELECTORS = /[︀-️]/g;
const ZERO_WIDTH_JOINER = /‍/g;
const KEYCAP_TAIL = /⃣/g;

export function stripEmoji(text) {
  if (!text) return '';
  return String(text)
    .replace(EMOJI_RANGES, '')
    .replace(VARIATION_SELECTORS, '')
    .replace(ZERO_WIDTH_JOINER, '')
    .replace(KEYCAP_TAIL, '')
    .replace(/[ \t]{2,}/g, ' ');
}

export function markdownToHtml(md) {
  return micromark(stripEmoji(md || ''), {
    allowDangerousHtml: false,
    extensions: [gfm()],
    htmlExtensions: [gfmHtml()],
  });
}

function escapeHtml(s) {
  return String(s ?? '').replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  }[c]));
}

function cssString(s) {
  return String(s || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}

// True when the brand carries an OWNER-UPLOADED logo (stored under workspace/
// brand/) rather than the bundled madarX default (brand-assets/). Drives whether
// branded artifacts render the uploaded logo as an <img> or fall back to the
// inline, currentColor-aware madarX lockup. A logo_data_url is required so the
// artifact stays self-contained inside its sandboxed iframe.
function isCustomBrandLogo(brand) {
  if (!brand?.logo_data_url) return false;
  const p = String(brand.logo_path || '');
  if (!p) return false;
  return !/brand-assets[/\\]madarx-logo-primary\.svg$/i.test(p);
}

function parseHex(hex) {
  if (!hex || typeof hex !== 'string') return null;
  const m = hex.trim().match(/^#?([0-9a-f]{6})$/i);
  if (!m) return null;
  const n = parseInt(m[1], 16);
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}

function isChromatic(hex) {
  const rgb = parseHex(hex);
  if (!rgb) return false;
  // Greyscale colors have R ≈ G ≈ B. A spread > 24 means the color carries hue.
  return Math.max(rgb.r, rgb.g, rgb.b) - Math.min(rgb.r, rgb.g, rgb.b) > 24;
}

function relLuminance(hex) {
  const rgb = parseHex(hex);
  if (!rgb) return 0;
  const f = (c) => {
    const s = c / 255;
    return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
  };
  return 0.2126 * f(rgb.r) + 0.7152 * f(rgb.g) + 0.0722 * f(rgb.b);
}

function contrastRatio(a, b) {
  const la = relLuminance(a);
  const lb = relLuminance(b);
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
  return (hi + 0.05) / (lo + 0.05);
}

function pickAccent(colors, { surface, primary }) {
  // Prefer a chromatic brand color with strong contrast against the document surface.
  // Falls back to any chromatic color, then the last palette entry, then the primary.
  const chromatic = colors
    .map(c => c?.value)
    .filter(v => v && isChromatic(v) && v !== surface && v !== primary);
  const withContrast = chromatic
    .map(value => ({ value, contrast: contrastRatio(value, surface) }))
    .sort((a, b) => b.contrast - a.contrast);
  const best = withContrast.find(c => c.contrast >= 3.5) || withContrast[0];
  if (best) return best.value;
  return colors[colors.length - 1]?.value || primary;
}

function brandCss(profile) {
  if (!profile?.active) return '';
  const colors = profile.colors || [];
  const primary = colors[0]?.value || '#0F0F11';
  const surface = colors[1]?.value || '#FAFAF7';
  const soft = colors[2]?.value || '#E8E8E5';
  // Muted needs contrast against the surface or body text becomes unreadable.
  // Skip primary/surface/soft and pick the first remaining color with usable contrast.
  const mutedCandidate = colors
    .map(c => c?.value)
    .filter(v => v && v !== surface && v !== primary && v !== soft && contrastRatio(v, surface) >= 3.0)[0];
  const muted = mutedCandidate || colors[3]?.value || '#68707B';
  const accent = pickAccent(colors, { surface, primary });
  const sans = profile.fonts?.[0] || 'Inter';
  const mono = profile.fonts?.find(f => /mono/i.test(f)) || 'IBM Plex Mono';
  // Link color must contrast with the surface. If accent fails, fall back to the primary text color.
  const linkColor = contrastRatio(accent, surface) >= 4.0 ? accent : primary;
  // Strong/bold text needs to stay readable too.
  const strongColor = contrastRatio(primary, surface) >= 4.5 ? primary : primary;
  return `
:root {
  --bg: ${surface};
  --fg: ${primary};
  --muted: ${muted};
  --accent: ${accent};
  --link: ${linkColor};
  --rule: ${soft};
  --rule-soft: ${soft};
  --code-bg: color-mix(in srgb, ${soft} 72%, white);
  --blockquote-bg: color-mix(in srgb, ${soft} 58%, white);
  --th-bg: color-mix(in srgb, ${soft} 64%, white);
}
body {
  background: var(--bg);
  color: var(--fg);
  font-family: "${cssString(sans)}", system-ui, -apple-system, "Segoe UI", sans-serif;
}
.doc-body a { color: ${linkColor}; text-decoration: underline; text-underline-offset: 2px; }
.doc-body a:hover { color: ${linkColor}; text-decoration-thickness: 2px; }
.doc-body strong { color: ${strongColor}; font-weight: 600; }
.doc-body blockquote { border-left-color: ${accent}; }
.doc-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 28px;
  color: var(--muted);
  font-family: "${cssString(mono)}", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.doc-brand img {
  width: 34px;
  max-height: 34px;
  object-fit: contain;
}
.doc-header { border-bottom-color: var(--rule); }
.doc-title { color: var(--fg); }
.doc-body h2 { border-bottom-color: var(--rule-soft); }
`;
}

const BASE_CSS = `
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
main.doc {
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  padding: clamp(20px, 5vw, 64px) clamp(16px, 4vw, 40px) clamp(28px, 6vw, 96px);
  box-sizing: border-box;
}
.doc-header { margin-bottom: clamp(24px, 4vw, 40px); padding-bottom: clamp(16px, 2.5vw, 24px); border-bottom: 1px solid var(--rule); }
.doc-title { margin: 0; }
.doc-body { word-wrap: break-word; overflow-wrap: break-word; }
.doc-body h1, .doc-body h2, .doc-body h3, .doc-body h4 { margin: 2em 0 0.6em; line-height: 1.2; letter-spacing: -0.01em; }
.doc-body h1 { font-size: clamp(1.35rem, 4.2vw, 1.9rem); }
.doc-body h2 { font-size: clamp(1.15rem, 3.4vw, 1.4rem); padding-bottom: 0.3em; border-bottom: 1px solid var(--rule-soft); }
.doc-body h3 { font-size: clamp(1.02rem, 2.6vw, 1.15rem); }
.doc-body h4 { font-size: clamp(0.95rem, 2.2vw, 1rem); }
.doc-body p { margin: 1em 0; }
.doc-body ul, .doc-body ol { padding-left: 1.4em; margin: 1em 0; }
.doc-body li { margin: 0.35em 0; }
.doc-body li > p { margin: 0.25em 0; }
.doc-body strong { font-weight: 600; }
.doc-body a { color: var(--link, var(--accent)); text-decoration: underline; text-underline-offset: 2px; }
.doc-body a:hover { text-decoration-thickness: 2px; }
.doc-body blockquote {
  margin: 1.4em 0; padding: 0.6em 1.1em;
  border-left: 3px solid var(--accent);
  color: var(--muted);
  background: var(--blockquote-bg);
}
.doc-body code {
  font-family: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace;
  font-size: 0.88em;
  padding: 2px 6px;
  background: var(--code-bg);
  border-radius: 4px;
}
.doc-body pre {
  background: var(--code-bg);
  border: 1px solid var(--rule-soft);
  border-radius: 8px;
  padding: 16px 18px;
  overflow-x: auto;
  margin: 1.2em 0;
  font-size: 0.9rem;
  line-height: 1.55;
}
.doc-body pre code { padding: 0; background: transparent; border-radius: 0; }
.doc-table-wrap { margin: 1.5em 0; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--rule); border-radius: 10px; }
.doc-body table { width: 100%; border-collapse: collapse; margin: 0; font-size: 0.9em; line-height: 1.5; font-variant-numeric: tabular-nums; }
.doc-body th, .doc-body td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--rule-soft); vertical-align: top; }
.doc-body thead th { font-weight: 600; background: var(--th-bg); border-bottom: 2px solid var(--rule); white-space: nowrap; font-size: 0.76rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.doc-body tbody tr:nth-child(even) { background: color-mix(in srgb, var(--th-bg) 40%, transparent); }
.doc-body tbody tr:last-child td { border-bottom: 0; }
.doc-body td code, .doc-body th code { white-space: nowrap; background: transparent; padding: 0; font-size: 0.92em; }
.doc-body hr { border: 0; border-top: 1px solid var(--rule-soft); margin: 2em 0; }
.doc-body img { max-width: 100%; height: auto; border-radius: 8px; }
.doc-body :is(h1,h2,h3,h4):first-child { margin-top: 0; }
`;

const STYLE_TOKENS = {
  balanced: `
:root {
  --bg: #FAFAF7;
  --fg: #111318;
  --muted: #5A5E66;
  --accent: #1F4FFF;
  --rule: #E5E5E0;
  --rule-soft: #ECECE6;
  --code-bg: #F1F1EB;
  --blockquote-bg: #F4F4EF;
  --th-bg: #F1F1EB;
}
body { background: var(--bg); color: var(--fg);
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: clamp(13.5px, 2.6vw, 16px); line-height: 1.65;
}
.doc-title { font-size: clamp(1.6rem, 6vw, 2.4rem); line-height: 1.05; letter-spacing: -0.035em; font-weight: 700; }
`,
  minimal: `
:root {
  --bg: #FFFFFF;
  --fg: #0A0A0A;
  --muted: #6B6B6B;
  --accent: #0A0A0A;
  --rule: #E8E8E8;
  --rule-soft: #F0F0F0;
  --code-bg: #F6F6F6;
  --blockquote-bg: #FAFAFA;
  --th-bg: #FAFAFA;
}
body { background: var(--bg); color: var(--fg);
  font-family: "Inter", system-ui, -apple-system, sans-serif;
  font-size: clamp(13px, 2.5vw, 15.5px); line-height: 1.7;
}
main.doc { max-width: 680px; padding: clamp(28px, 7vw, 88px) clamp(16px, 4vw, 40px) clamp(36px, 8vw, 120px); }
.doc-title { font-size: clamp(1.5rem, 5.6vw, 2.2rem); line-height: 1.1; letter-spacing: -0.04em; font-weight: 500; }
.doc-header { border-bottom-color: #EFEFEF; }
.doc-body h2 { border-bottom-color: #F2F2F2; font-weight: 500; }
.doc-body a { text-decoration-color: #BBB; }
`,
  bold: `
:root {
  --bg: #0E0F12;
  --fg: #F6F6F2;
  --muted: #A6A8AF;
  --accent: #FF5B2E;
  --rule: #1F2127;
  --rule-soft: #1A1C22;
  --code-bg: #15171C;
  --blockquote-bg: #15171C;
  --th-bg: #15171C;
}
body { background: var(--bg); color: var(--fg);
  font-family: "Inter", system-ui, -apple-system, sans-serif;
  font-size: clamp(13.5px, 2.6vw, 16px); line-height: 1.65;
}
.doc-title { font-size: clamp(1.8rem, 7.5vw, 3rem); line-height: 0.98; letter-spacing: -0.045em; font-weight: 800; }
.doc-body h2 { font-weight: 700; }
.doc-body a { color: var(--accent); }
.doc-body strong { color: #FFFFFF; }
`,
  soft: `
:root {
  --bg: #FBF7F1;
  --fg: #2A2620;
  --muted: #6E665B;
  --accent: #B85C38;
  --rule: #EDE5D7;
  --rule-soft: #F2EBDD;
  --code-bg: #F4ECDC;
  --blockquote-bg: #F4ECDC;
  --th-bg: #F4ECDC;
}
body { background: var(--bg); color: var(--fg);
  font-family: "Source Serif Pro", Georgia, "Iowan Old Style", serif;
  font-size: clamp(14px, 2.7vw, 17px); line-height: 1.7;
}
.doc-title { font-family: "Inter", system-ui, sans-serif; font-size: clamp(1.55rem, 5.8vw, 2.3rem); line-height: 1.1; letter-spacing: -0.03em; font-weight: 600; }
.doc-body h2, .doc-body h3, .doc-body h4 { font-family: "Inter", system-ui, sans-serif; }
.doc-body pre, .doc-body code, .doc-body table, .doc-body img { border-radius: 12px; }
.doc-body pre { border-radius: 14px; }
`,
};

function styleCss(style) {
  return STYLE_TOKENS[style] || STYLE_TOKENS.balanced;
}

// Wrap every GFM table in a horizontally-scrollable container so wide tables
// (commit logs, comparison matrices) never overflow the document column or the
// artifact window — they scroll inside their own frame instead.
function wrapTables(html) {
  return String(html || '')
    .replace(/<table>/g, '<div class="doc-table-wrap"><table>')
    .replace(/<\/table>/g, '</table></div>');
}

export function renderDocumentHtml({ title, markdown, style, meta }) {
  const actualStyle = style || getDesignStyle();
  const brand = getBrandProfile();
  const body = wrapTables(markdownToHtml(markdown || ''));
  const css = `${styleCss(actualStyle)}\n${brandCss(brand)}\n${BASE_CSS}`;
  const metaLine = meta
    ? `<div class="doc-meta" style="margin-top:14px;font-size:13px;color:var(--muted);">${escapeHtml(meta)}</div>`
    : '';
  const brandMark = brand?.active
    ? `<div class="doc-brand">${brand.logo_data_url ? `<img src="${brand.logo_data_url}" alt="">` : ''}<span>${escapeHtml(brand.company_name || 'Brand')}</span></div>`
    : '';
  return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escapeHtml(title || 'madarX artifact')}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Source+Serif+Pro:wght@400;600&display=swap" rel="stylesheet">
<style>${css}</style>
</head>
<body>
<main class="doc">
  ${brandMark}
  <header class="doc-header">
    <h1 class="doc-title">${escapeHtml(title || 'Untitled')}</h1>${metaLine}
  </header>
  <article class="doc-body">${body}</article>
</main>
</body>
</html>`;
}

// ---------------------------------------------------------------------------
// Release notes ("What's new") — dedicated branded template
// ---------------------------------------------------------------------------
// The generic document renderer above is correct for arbitrary reports, but the
// release-notes artifact that lands on the main space after every update earns a
// purpose-built template: a dark editorial "What's new" card that honors the
// madarX brand (Onyx surface, Platinum text, Mars accent, Inter + IBM Plex Mono)
// and groups the CHANGELOG section into scannable Added / Fixed / Changed rows.

// Map a `### GroupName` changelog heading to a stable kind + dot color.
// Restrained palette — Mars only earns the eye on new capability (Added); fixes
// and chores stay greyscale so a release of pure bugfixes doesn't shout.
function changelogGroupMeta(label) {
  const key = String(label || '').trim().toLowerCase();
  if (/^add/.test(key) || /new|feature/.test(key)) return { kind: 'added', dot: 'var(--cl-accent)' };
  if (/^fix/.test(key) || /bug/.test(key)) return { kind: 'fixed', dot: 'var(--cl-fixed)' };
  if (/^chang|^improv|^updat|^enhanc/.test(key)) return { kind: 'changed', dot: 'var(--cl-accent)' };
  if (/^chore|^intern|^maint|^docs?$|^deps?/.test(key)) return { kind: 'chore', dot: 'var(--cl-chore)' };
  return { kind: 'other', dot: 'var(--cl-fixed)' };
}

// Parse a single `## [version] - date` CHANGELOG section into structured groups.
// Each group holds top-level bullets; bullet continuation + nested bullets ride
// along as the item's markdown body so code spans, links, and sub-lists survive.
function parseChangelogSection(md) {
  const lines = String(md || '').split('\n');
  let version = '';
  let date = '';
  const groups = [];
  let group = null;
  let item = null;
  const flushItem = () => {
    if (item != null && group) group.items.push(item.join('\n').trim());
    item = null;
  };
  for (const raw of lines) {
    const h2 = raw.match(/^##\s*\[([^\]]+)\]\s*(?:-\s*(.+))?$/);
    if (h2) { version = h2[1].trim(); date = (h2[2] || '').trim(); continue; }
    const h3 = raw.match(/^###\s+(.+?)\s*$/);
    if (h3) { flushItem(); group = { label: h3[1].trim(), items: [] }; groups.push(group); continue; }
    const top = raw.match(/^[-*]\s+(.*)$/);
    if (top) {
      // A bullet before any `### Group` heading still deserves a home.
      if (!group) { group = { label: 'Changes', items: [] }; groups.push(group); }
      flushItem();
      item = [top[1]];
      continue;
    }
    // Continuation or nested line — de-indent one level so nested bullets render
    // as a proper sub-list inside the item body.
    if (item != null) item.push(raw.replace(/^\s{0,2}/, ''));
  }
  flushItem();
  return { version, date, groups: groups.filter(g => g.items.length) };
}

// Render inline markdown (bold, code, links) without the wrapping <p>.
function inlineMd(s) {
  return markdownToHtml(String(s || '').trim())
    .replace(/^\s*<p>/, '')
    .replace(/<\/p>\s*$/, '')
    .trim();
}

// Split a bullet into its bold lead sentence + the remaining body markdown.
function splitChangelogItem(mdItem) {
  const m = String(mdItem || '').match(/^\*\*([\s\S]+?)\*\*\.?[ \t]*/);
  if (m) {
    return { title: inlineMd(m[1]), body: mdItem.slice(m[0].length).trim() };
  }
  return { title: '', body: String(mdItem || '').trim() };
}

function prettyChangelogDate(date) {
  const m = String(date || '').match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return String(date || '').trim();
  const months = ['January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'];
  const mi = parseInt(m[2], 10) - 1;
  return months[mi] ? `${months[mi]} ${parseInt(m[3], 10)}, ${m[1]}` : date;
}

const CHANGELOG_CSS = `
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--cl-bg);
  color: var(--cl-fg);
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "ss01", "cv11";
}
.cl-stage {
  min-height: 100%;
  padding: clamp(20px, 5vw, 56px) clamp(16px, 4vw, 44px) clamp(40px, 8vw, 96px);
  background:
    radial-gradient(120% 60% at 85% -8%, color-mix(in srgb, var(--cl-accent) 16%, transparent), transparent 60%),
    var(--cl-bg);
}
.cl-doc { width: 100%; max-width: 680px; margin: 0 auto; }
.cl-mark { display: flex; align-items: center; margin-bottom: clamp(30px, 6vw, 56px); color: var(--cl-fg); }
.cl-logo { display: block; flex: none; height: 30px; width: auto; }
.cl-logo-img { max-width: 220px; object-fit: contain; }
.cl-eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--cl-accent); font-weight: 500; margin: 0 0 16px;
}
.cl-headline {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 16px;
  margin: 0 0 12px;
}
.cl-version {
  margin: 0;
  font-size: clamp(1.9rem, 6vw, 2.7rem);
  line-height: 1.0; letter-spacing: -0.025em; font-weight: 500;
  color: var(--cl-fg);
}
.cl-sig { width: 56px; height: 2px; background: var(--cl-accent); margin: clamp(26px, 5vw, 38px) 0 clamp(26px, 5vw, 38px); border-radius: 2px; }
.cl-pill {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--cl-accent); font-weight: 600;
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--cl-accent) 42%, transparent);
  background: color-mix(in srgb, var(--cl-accent) 10%, transparent);
  transform: translateY(-4px);
}
.cl-pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--cl-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cl-accent) 24%, transparent);
}
.cl-sub {
  font-size: 14px; color: var(--cl-muted); margin: 0 0 clamp(28px, 5vw, 40px);
  font-variant-numeric: tabular-nums;
}
.cl-rule { height: 1px; border: 0; margin: 0 0 clamp(26px, 5vw, 38px);
  background: linear-gradient(90deg, var(--cl-line) 0%, color-mix(in srgb, var(--cl-line) 30%, transparent) 70%, transparent 100%); }
.cl-group { margin: 0 0 clamp(28px, 5vw, 40px); }
.cl-group:last-of-type { margin-bottom: 0; }
.cl-group-head {
  display: flex; align-items: center; gap: 12px;
  margin: 0 0 18px;
}
.cl-group-label {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--cl-fg-dim); font-weight: 600; white-space: nowrap;
}
.cl-group-label::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--cl-dot, var(--cl-fixed));
}
.cl-group-line { flex: 1; height: 1px; background: var(--cl-line); }
.cl-count {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--cl-muted); font-variant-numeric: tabular-nums;
}
.cl-item {
  position: relative;
  padding: 16px 18px 16px 20px;
  border: 1px solid var(--cl-line);
  border-radius: 14px;
  background: var(--cl-card);
  margin: 0 0 10px;
  transition: border-color 160ms ease, background 160ms ease;
}
.cl-item:last-child { margin-bottom: 0; }
.cl-item::before {
  content: ""; position: absolute; left: 0; top: 16px; bottom: 16px; width: 2px;
  border-radius: 2px; background: var(--cl-dot, var(--cl-fixed)); opacity: 0.55;
}
.cl-item:hover { border-color: color-mix(in srgb, var(--cl-dot, var(--cl-fixed)) 50%, var(--cl-line)); }
.cl-item-title { font-weight: 500; color: var(--cl-fg); line-height: 1.4; letter-spacing: -0.012em; font-size: 15.5px; }
.cl-item-body { color: var(--cl-body); font-size: 14px; line-height: 1.66; margin-top: 6px; }
.cl-item-body > :first-child { margin-top: 0; }
.cl-item-body > :last-child { margin-bottom: 0; }
.cl-item-body p { margin: 0.6em 0; }
.cl-item-body ul { margin: 0.5em 0; padding-left: 1.2em; }
.cl-item-body li { margin: 0.3em 0; }
.cl-item-body li::marker { color: color-mix(in srgb, var(--cl-dot, var(--cl-fixed)) 70%, var(--cl-muted)); }
.cl-item-body strong { color: var(--cl-fg); font-weight: 500; }
.cl-item-body a { color: var(--cl-accent); text-decoration: underline; text-underline-offset: 2px; }
.cl-item-body code {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em; padding: 1px 6px; border-radius: 5px;
  background: color-mix(in srgb, var(--cl-fg) 8%, transparent);
  color: var(--cl-fg-dim);
  border: 1px solid color-mix(in srgb, var(--cl-fg) 8%, transparent);
}
.cl-item-title code { font-size: 0.86em; padding: 1px 6px; border-radius: 5px;
  background: color-mix(in srgb, var(--cl-fg) 8%, transparent);
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; }
.cl-foot {
  display: flex; align-items: center; gap: 9px;
  margin-top: clamp(34px, 6vw, 52px); padding-top: 20px;
  border-top: 1px solid var(--cl-line);
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.06em; color: var(--cl-muted);
}
.cl-foot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--cl-accent); }
`;

export function renderChangelogHtml({ version, date, markdown, title } = {}) {
  const brand = getBrandProfile();
  const parsed = parseChangelogSection(markdown || '');
  const ver = (version || parsed.version || '').replace(/^v/i, '');
  const when = prettyChangelogDate(date || parsed.date);
  const groups = parsed.groups;
  const company = brand?.company_name || 'madarX';

  // Brand-derived palette with safe dark-editorial fallbacks. bg=Onyx, fg=Platinum,
  // accent=Mars (the chromatic high-contrast pick), muted=Graphite.
  const colors = (brand?.colors || []).map(c => c?.value).filter(Boolean);
  const bg = colors[0] || '#0F0F11';
  const fg = colors[1] || '#E8E8E5';
  const accent = pickAccent(brand?.colors || [], { surface: bg, primary: fg }) || '#C2452A';
  const muted = colors[3] || '#8A8278';
  const bone = colors[2] || '#DCDCD8';
  const mars = '#C2452A';
  // The brand logo. When the owner has uploaded a custom logo (it lives under
  // workspace/brand/ rather than the bundled brand-assets/ default), render it
  // as an <img> so the release notes carry *their* identity, not madarX's. For
  // the default brand we keep the inline madarX lockup (brand-assets/
  // madarx-logo-primary.svg): orbit mark + "madarX" wordmark in IBM Plex Mono,
  // X in Mars. It is inlined (not <img>) so its `currentColor` ring + wordmark
  // resolve to Platinum on Onyx; an iframe <img> would render currentColor black.
  const logo = isCustomBrandLogo(brand)
    ? `<img class="cl-logo cl-logo-img" src="${escapeHtml(brand.logo_data_url)}" alt="${escapeHtml(company)}">`
    : `<svg class="cl-logo" viewBox="0 0 360 100" height="30" role="img" aria-label="${escapeHtml(company)}">
    <g transform="translate(50,50)">
      <path d="M 35.32 -18.78 A 40 40 0 1 1 28.78 -27.80" stroke="currentColor" stroke-width="3" fill="none"/>
      <line x1="-28.28" y1="-28.28" x2="28.28" y2="28.28" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
      <line x1="-28.28" y1="28.28" x2="28.28" y2="-28.28" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
      <circle cx="32.36" cy="-23.51" r="5" fill="${mars}"/>
    </g>
    <text x="115" y="64" font-family="'IBM Plex Mono','Menlo',monospace" font-size="42" font-weight="500" fill="currentColor" letter-spacing="-0.02em">madar<tspan fill="${mars}">X</tspan></text>
  </svg>`;

  const vars = `
:root {
  --cl-bg: ${bg};
  --cl-fg: ${fg};
  --cl-fg-dim: color-mix(in srgb, ${fg} 82%, ${muted});
  --cl-body: ${bone};
  --cl-muted: color-mix(in srgb, ${muted} 78%, ${fg});
  --cl-accent: ${accent};
  --cl-fixed: color-mix(in srgb, ${fg} 46%, ${bg});
  --cl-chore: color-mix(in srgb, ${muted} 60%, ${bg});
  --cl-card: color-mix(in srgb, ${fg} 4%, ${bg});
  --cl-line: color-mix(in srgb, ${fg} 12%, transparent);
}`;

  const groupsHtml = groups.map(group => {
    const meta = changelogGroupMeta(group.label);
    const items = group.items.map(raw => {
      const { title: t, body } = splitChangelogItem(raw);
      const bodyHtml = body ? `<div class="cl-item-body">${wrapTables(markdownToHtml(body))}</div>` : '';
      const titleHtml = t ? `<div class="cl-item-title">${t}</div>` : '';
      // Title-less items (rare) fold the body up as the headline line.
      const inner = t ? `${titleHtml}${bodyHtml}` : `<div class="cl-item-title">${inlineMd(body)}</div>`;
      return `      <div class="cl-item" style="--cl-dot:${meta.dot};">${inner}</div>`;
    }).join('\n');
    const n = group.items.length;
    return `    <section class="cl-group">
      <div class="cl-group-head">
        <span class="cl-group-label" style="--cl-dot:${meta.dot};">${escapeHtml(group.label)}</span>
        <span class="cl-group-line"></span>
        <span class="cl-count">${n} ${n === 1 ? 'change' : 'changes'}</span>
      </div>
${items}
    </section>`;
  }).join('\n');

  const verLabel = ver ? `v${escapeHtml(ver)}` : escapeHtml(company);
  const docTitle = title || `${company} ${ver ? `v${ver} ` : ''}— What's new`;

  return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escapeHtml(docTitle)}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>${vars}${CHANGELOG_CSS}</style>
</head>
<body>
<div class="cl-stage">
  <div class="cl-doc">
    <div class="cl-mark">${logo}</div>
    <p class="cl-eyebrow">Release notes</p>
    <div class="cl-headline">
      <h1 class="cl-version">What's new</h1>
      <span class="cl-pill">${verLabel}</span>
    </div>
    <p class="cl-sub">${when ? `Shipped ${escapeHtml(when)}` : 'Latest update'}</p>
    <div class="cl-sig"></div>
${groupsHtml || '    <p class="cl-sub">No notable changes recorded for this release.</p>'}
    <div class="cl-foot">${escapeHtml(company)} &nbsp;·&nbsp; local-first AI workforce</div>
  </div>
</div>
</body>
</html>`;
}
