import fs from "node:fs/promises";
import path from "node:path";

const outputArg = process.argv[2];
const outputDir = outputArg
  ? path.resolve(outputArg)
  : path.resolve(process.cwd(), "outputs", "wechat-skill-run");
const outputPath = path.join(outputDir, "公众号_真运行时判断.html");
const userInput = process.argv[3] || process.env.SKILL_USER_INPUT || "";
const ARTICLE_IMG_STYLE = "width:100%;max-width:660px;height:auto;display:block;margin:20px 0;border:0;border-radius:0;";
const HEADER_IMG_STYLE = "width:100%;max-width:660px;height:auto;display:block;margin:0 0 20px;border:0;border-radius:0;";
const CHART_IMG_STYLE = "width:100%;max-width:660px;height:auto;display:block;margin:0;border:0;border-radius:0;";

function escapeHtml(value) {
  return String(value ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function pngImg(id, alt, style = ARTICLE_IMG_STYLE, className = "article-img") {
  const classAttr = className ? ` class="${className}"` : "";
  return `<img id="${id}"${classAttr} width="660" style="${style}" alt="${escapeHtml(alt)}">`;
}

function stripCodeFence(value) {
  const text = String(value || "").trim();
  const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
  return match ? match[1].trim() : text;
}

function normalizeLines(value, fallback = []) {
  if (Array.isArray(value)) return value.map((item) => String(item || "").trim()).filter(Boolean);
  if (typeof value === "string") return value.split(/\n+/).map((item) => item.trim()).filter(Boolean);
  return fallback;
}

function normalizeArticle(raw) {
  const sections = Array.isArray(raw?.sections) ? raw.sections : [];
  return {
    title: String(raw?.title || "流程真正 AI 化：不是多一个聊天框，而是多一段可交付的能力").trim(),
    deck: String(raw?.deck || raw?.subtitle || "从流程、规则、执行器和验收闭环重新看企业 AI。").trim(),
    opening: normalizeLines(raw?.opening, [
      "很多企业说自己在做 AI。",
      "但你往真实流程里一放，就会发现问题不在模型会不会回答，而在它能不能接住一段工作。",
    ]).slice(0, 6),
    sections: sections.map((section, index) => ({
      heading: String(section?.heading || `第 ${index + 1} 层：把流程变成可执行能力`).trim(),
      paragraphs: normalizeLines(section?.paragraphs, []).slice(0, 8),
    })).filter((section) => section.paragraphs.length > 0).slice(0, 7),
    goldLines: normalizeLines(raw?.goldLines, [
      "企业 AI 的分水岭，不是会不会聊天，而是能不能接住一段流程。",
      "流程不是文档，流程是可以被触发、被执行、被验收的能力。",
    ]).slice(0, 4),
    actionList: normalizeLines(raw?.actionList, [
      "先选一个高频流程，写清楚输入、规则、动作和交付物。",
      "再给它配置可执行入口，让系统生成真实文件。",
      "最后用验收标准检查结果，而不是只看回答是否顺耳。",
    ]).slice(0, 6),
    conclusion: normalizeLines(raw?.conclusion, [
      "所以流程 AI 化的第一步，不是把所有岗位都改造成 Agent。",
      "更现实的做法，是从一个具体流程开始，把规则沉淀成 Skill，把执行交给运行时，把结果放进可验收的交付物里。",
      "这条路慢一点，但更像真正的生产系统。",
    ]).slice(0, 6),
  };
}

function articleTextLength(article) {
  return [
    article.title,
    article.deck,
    ...article.opening,
    ...article.sections.flatMap((section) => [section.heading, ...section.paragraphs]),
    ...article.goldLines,
    ...article.actionList,
    ...article.conclusion,
  ].join("").replace(/\s+/g, "").length;
}

const CN_SECTION_NUMBERS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];

function withChineseSectionNumber(heading, index) {
  const text = String(heading || "").trim();
  if (/^[一二三四五六七八九十]+、/.test(text)) return text;
  const num = CN_SECTION_NUMBERS[index] || String(index + 1);
  return `${num}、${text}`;
}

async function callQwenJson({ apiKey, model, messages, maxTokens, purpose }) {
  const started = Date.now();
  const response = await fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model,
      messages,
      temperature: 0.72,
      max_tokens: maxTokens,
      response_format: { type: "json_object" },
    }),
  });

  if (!response.ok) {
    const body = await response.text().catch(() => "");
    throw new Error(`Qwen 调用失败: HTTP ${response.status} ${body.slice(0, 240)}`);
  }

  const payload = await response.json();
  const content = payload?.choices?.[0]?.message?.content;
  if (!content) {
    throw new Error("Qwen 返回为空，拒绝生成静态模板");
  }

  let parsed;
  try {
    parsed = JSON.parse(stripCodeFence(content));
  } catch (error) {
    throw new Error(`Qwen JSON 解析失败: ${error instanceof Error ? error.message : String(error)}`);
  }

  console.error(`MODEL_CALL_OK model=${model} purpose=${purpose} durationMs=${Date.now() - started} chars=${content.length}`);
  return parsed;
}

async function ensureArticleLength(article, input, apiKey, model) {
  if (articleTextLength(article) >= 1900) return article;

  const expansionPrompt = [
    "请扩写这篇公众号文章，目标是让可见正文超过2200个中文字符。",
    "要求：只补充新的正文，不要重复已有段落；继续保持短句、真实场景、工程落地感；不要写运行时测试。",
    "返回 JSON：{\"sections\":[{\"heading\":\"新增小标题\",\"paragraphs\":[\"新增段落\"]}],\"actionList\":[\"新增行动建议\"],\"conclusion\":[\"新增结尾段落\"]}",
    "",
    `用户原始输入：${input || "写一篇关于流程 + AI 的公众号文章。"}`,
    "",
    `当前文章 JSON：${JSON.stringify(article).slice(0, 6000)}`,
  ].join("\n");

  const expanded = await callQwenJson({
    apiKey,
    model,
    purpose: "expand",
    maxTokens: 3600,
    messages: [
      { role: "system", content: "你是公众号文章扩写编辑，只输出可解析 JSON。" },
      { role: "user", content: expansionPrompt },
    ],
  });
  const extra = normalizeArticle({
    title: article.title,
    deck: article.deck,
    opening: [],
    sections: expanded.sections || [],
    goldLines: [],
    actionList: expanded.actionList || [],
    conclusion: expanded.conclusion || [],
  });

  return {
    ...article,
    sections: [...article.sections, ...extra.sections].slice(0, 9),
    actionList: [...article.actionList, ...extra.actionList].slice(0, 8),
    conclusion: [...article.conclusion, ...extra.conclusion].slice(0, 10),
  };
}

async function generateArticleDraft(input) {
  const apiKey = process.env.QWEN_API_KEY;
  if (!apiKey) {
    throw new Error("QWEN_API_KEY 未设置，拒绝用静态模板冒充模型生成");
  }

  const model = process.env.WECHAT_SKILL_MODEL || process.env.LLM_DEFAULT_CHAT_MODEL || "qwen-plus";
  const prompt = [
    "你是公众号写作专家。请基于用户输入，创作一篇适合微信公众号发布的中文文章。",
    "硬性要求：",
    "1. 必须围绕用户真实主题写，不要写运行时测试、不要复述这些规则。",
    "2. 禁止套话开头，禁止排比腔，少用 AI 味过渡词。",
    "3. 主题如果涉及企业 AI、流程管理、Agent、数字化转型，要采用“反虚纠偏 + 真实场景 + 三层拆解 + 落地路径”的结构。",
    "4. 正文可见文字不少于2200个中文字符，至少5个 sections，每个 section 至少3段。",
    "5. 段落短，平均句长短，结尾要给出可执行方法。",
    "6. 每个 section.heading 必须用中文大写序号开头，例如“一、真正的问题是什么”“二、先从哪个场景开始”。",
    "7. 只返回 JSON，不要 Markdown，不要解释。",
    "",
    "JSON 结构：",
    "{",
    "  \"title\": \"标题\",",
    "  \"deck\": \"一句话导语\",",
    "  \"opening\": [\"开头段落1\", \"开头段落2\"],",
    "  \"sections\": [{\"heading\":\"小标题\", \"paragraphs\":[\"段落\"]}],",
    "  \"goldLines\": [\"金句1\", \"金句2\"],",
    "  \"actionList\": [\"行动建议\"],",
    "  \"conclusion\": [\"结尾段落\"]",
    "}",
    "",
    `用户输入：${input || "写一篇关于流程 + AI 的公众号文章。"}`
  ].join("\n");

  const parsed = await callQwenJson({
    apiKey,
    model,
    purpose: "draft",
    maxTokens: 5200,
    messages: [
      { role: "system", content: "你只输出可解析 JSON。文章必须像真人写给企业管理者和产品负责人看的公众号。" },
      { role: "user", content: prompt },
    ],
  });

  return ensureArticleLength(normalizeArticle(parsed), input, apiKey, model);
}

function renderGeneratedArticle(article) {
  const parts = [];
  parts.push(`<h1>${escapeHtml(article.title)}</h1>`);
  parts.push(`<p><strong>${escapeHtml(article.deck)}</strong></p>`);
  for (const paragraph of article.opening) {
    parts.push(`<p>${escapeHtml(paragraph)}</p>`);
  }
  parts.push(pngImg("gold-1", "金句"));
  article.sections.forEach((section, index) => {
    parts.push(`<h2>${escapeHtml(withChineseSectionNumber(section.heading, index))}</h2>`);
    for (const paragraph of section.paragraphs) {
      parts.push(`<p>${escapeHtml(paragraph)}</p>`);
    }
    if (index === 0) {
      parts.push(`<div class="infographic">${pngImg("chart1", "配图", CHART_IMG_STYLE, "")}<p class="image-caption">图1：从聊天到流程执行的变化</p></div>`);
    } else if (index === 1) {
      parts.push(pngImg("box-info", "事实核查"));
    } else if (index === 2) {
      parts.push(`<div class="infographic">${pngImg("chart2", "配图", CHART_IMG_STYLE, "")}<p class="image-caption">图2：流程 Skill 的执行闭环</p></div>`);
    } else if (index === 3) {
      parts.push(pngImg("step-1", "步骤1"));
      parts.push(pngImg("step-2", "步骤2"));
      parts.push(pngImg("step-3", "步骤3"));
      parts.push(pngImg("step-4", "步骤4"));
    } else if (index === 4) {
      parts.push(`<div class="infographic">${pngImg("chart3", "配图", CHART_IMG_STYLE, "")}<p class="image-caption">图3：验收真实 AI 流程能力的三张账</p></div>`);
    }
  });
  parts.push(`<h2>${escapeHtml(withChineseSectionNumber("明天就能开始的动作", article.sections.length))}</h2>`);
  for (const item of article.actionList) {
    parts.push(`<p>• ${escapeHtml(item)}</p>`);
  }
  for (const paragraph of article.conclusion) {
    parts.push(`<p>${escapeHtml(paragraph)}</p>`);
  }
  parts.push(pngImg("gold-2", "金句"));
  parts.push(pngImg("cta-img", "行动号召"));
  return parts.join("\n\n  ");
}

const article = await generateArticleDraft(userInput);
console.error(`ARTICLE_LENGTH chars=${articleTextLength(article)}`);
const visualData = {
  title: article.title,
  deck: article.deck,
  goldLines: article.goldLines,
  actionList: article.actionList,
};

const html = `<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>${escapeHtml(article.title)}</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
      font-size: 15px;
      line-height: 1.8;
      color: #333;
      background: #fff;
      max-width: 677px;
      margin: 0 auto;
      padding: 20px;
    }
    h1 { font-size: 22px; font-weight: bold; color: #1a1a1a; margin: 30px 0 15px; }
    h2 { font-size: 18px; font-weight: bold; color: #1a1a1a; border-left: 4px solid #1890ff; padding-left: 12px; margin: 30px 0 15px; }
    h3 { font-size: 16px; font-weight: bold; color: #333; margin: 20px 0 10px; }
    p { margin-bottom: 16px; }
    strong { color: #1a1a1a; }
    code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: #5267d9; }
    a { color: #5267d9; text-decoration: none; }
    .header-img { width: 100%; display: block; margin: 0 0 20px; border-radius: 12px; }
    .author-info { color: #999; font-size: 13px; margin-bottom: 24px; }
    .article-img { width: 100%; display: block; margin: 20px 0; border-radius: 12px; }
    .infographic { margin: 32px 0; }
    .infographic img { width: 100%; border-radius: 12px; display: block; }
    .image-caption { font-size: 13px; color: #999; margin-top: 10px; font-style: italic; }
    .source-note { font-size: 13px; color: #777; line-height: 1.8; margin-top: 24px; }
    .divider { text-align: center; color: #ccc; font-size: 20px; letter-spacing: 8px; margin: 30px 0; }
    .copy-btn {
      position: fixed;
      bottom: 30px;
      right: 30px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: #fff;
      border: none;
      padding: 14px 24px;
      border-radius: 50px;
      font-size: 15px;
      cursor: pointer;
      box-shadow: 0 4px 20px rgba(102,126,234,0.4);
      z-index: 999;
      transition: all 0.3s;
    }
    .copy-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(102,126,234,0.5); }
    .toast {
      position: fixed;
      bottom: 90px;
      right: 30px;
      background: #52c41a;
      color: #fff;
      padding: 10px 20px;
      border-radius: 8px;
      font-size: 14px;
      opacity: 0;
      transition: opacity 0.3s;
      z-index: 1000;
    }
    .toast.show { opacity: 1; }
    @media (max-width: 677px) {
      body { padding: 16px; }
      .copy-btn { bottom: 20px; right: 20px; padding: 12px 20px; font-size: 14px; }
    }
  </style>
</head>
<body>
<div id="article-content">
  ${pngImg("header-img", "头图", HEADER_IMG_STYLE, "header-img")}

  <p class="author-info">詹老师 · AI产品专家 / 流程管理专家</p>

  ${renderGeneratedArticle(article)}

  <p class="source-note">核查依据：
    <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL">MDN Canvas toDataURL</a>、
    <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static">MDN URL.createObjectURL</a>、
    <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand">MDN Document.execCommand</a>。
  </p>

  <p class="divider">···</p>

  ${pngImg("author-card", "作者名片")}
</div><!-- end article-content -->

<button class="copy-btn" onclick="copyArticle()">一键复制全文</button>
<div id="toast" class="toast">已复制公众号富文本</div>

<script>
  const DPR = 3;
  const ARTICLE_VISUALS = ${JSON.stringify(visualData).replace(/</g, "\\u003c")};

  function roundRect(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
    ctx.lineTo(x + w, y + h - r);
    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    ctx.lineTo(x + r, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.closePath();
  }

  function canvasToPng(drawFn, elId, W, H) {
    const c = document.createElement('canvas');
    c.width = W * DPR;
    c.height = H * DPR;
    const ctx = c.getContext('2d');
    ctx.scale(DPR, DPR);
    drawFn(ctx, W, H);
    document.getElementById(elId).src = c.toDataURL('image/png', 1.0);
  }

  function text(ctx, value, x, y, size, color, weight, align) {
    ctx.fillStyle = color || '#333';
    ctx.font = (weight ? weight + ' ' : '') + size + 'px PingFang SC, Microsoft YaHei, Arial, sans-serif';
    ctx.textAlign = align || 'left';
    ctx.fillText(value, x, y);
  }

  function wrapText(ctx, value, x, y, maxWidth, lineHeight) {
    ctx.textAlign = 'left';
    const chars = Array.from(value);
    let line = '';
    for (let i = 0; i < chars.length; i += 1) {
      const test = line + chars[i];
      if (ctx.measureText(test).width > maxWidth && line) {
        ctx.fillText(line, x, y);
        line = chars[i];
        y += lineHeight;
      } else {
        line = test;
      }
    }
    if (line) ctx.fillText(line, x, y);
  }

  function drawHeader(ctx, W, H) {
    const bg = ctx.createLinearGradient(0, 0, W, H);
    bg.addColorStop(0, '#f9fbff');
    bg.addColorStop(1, '#e9edff');
    ctx.fillStyle = bg;
    roundRect(ctx, 0, 0, W, H, 18);
    ctx.fill();
    ctx.fillStyle = 'rgba(102,126,234,0.14)';
    ctx.beginPath(); ctx.arc(86, 72, 42, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = 'rgba(118,75,162,0.12)';
    ctx.beginPath(); ctx.arc(580, 208, 58, 0, Math.PI * 2); ctx.fill();
    const pill = ctx.createLinearGradient(54, 54, 174, 98);
    pill.addColorStop(0, '#667eea');
    pill.addColorStop(1, '#764ba2');
    ctx.fillStyle = pill;
    roundRect(ctx, 54, 54, 128, 42, 21);
    ctx.fill();
    text(ctx, '公众号成稿', 118, 81, 15, '#fff', 'bold', 'center');
    ctx.fillStyle = '#1f2430';
    ctx.font = 'bold 29px PingFang SC, Microsoft YaHei, Arial, sans-serif';
    wrapText(ctx, ARTICLE_VISUALS.title || '流程真正 AI 化', 54, 150, 360, 36);
    ctx.fillStyle = '#667085';
    ctx.font = '15px PingFang SC, Microsoft YaHei, Arial, sans-serif';
    wrapText(ctx, ARTICLE_VISUALS.deck || '从流程、规则和交付物看企业 AI', 54, 228, 360, 24);
    ctx.fillStyle = '#ffffff';
    roundRect(ctx, 424, 60, 150, 120, 16);
    ctx.fill();
    ctx.strokeStyle = '#dfe6f3';
    ctx.stroke();
    ctx.fillStyle = '#667eea';
    roundRect(ctx, 444, 88, 92, 10, 5);
    ctx.fill();
    ctx.fillStyle = '#c9d3f8';
    roundRect(ctx, 444, 116, 108, 8, 4);
    ctx.fill();
    ctx.fillStyle = '#dfe6f3';
    roundRect(ctx, 444, 140, 78, 8, 4);
    ctx.fill();
    ctx.fillStyle = '#764ba2';
    ctx.beginPath();
    ctx.moveTo(536, 196);
    ctx.lineTo(580, 218);
    ctx.lineTo(536, 240);
    ctx.closePath();
    ctx.fill();
  }

  function drawGold(ctx, W, H, lines) {
    const g = ctx.createLinearGradient(0, 0, W, H);
    g.addColorStop(0, '#fffbe6');
    g.addColorStop(1, '#fff7cc');
    ctx.fillStyle = g;
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    ctx.strokeStyle = '#ffd666';
    ctx.lineWidth = 1.5;
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.stroke();
    lines.forEach((line, i) => text(ctx, line, W / 2, H / 2 - (lines.length - 1) * 14 + i * 31, 17, '#ad6800', 'bold', 'center'));
  }

  function drawBlueBox(ctx, W, H) {
    const g = ctx.createLinearGradient(0, 0, W, H);
    g.addColorStop(0, '#f0f5ff');
    g.addColorStop(1, '#e6f7ff');
    ctx.fillStyle = g;
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    ctx.strokeStyle = '#91d5ff';
    ctx.lineWidth = 1.5;
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.stroke();
    text(ctx, '联网核查结论', 24, 34, 16, '#1f3b7a', 'bold');
    text(ctx, 'Canvas 可导出 PNG 数据地址，适合复制型图文交付。', 24, 66, 15, '#2f3a4f');
    text(ctx, 'Blob URL 可承载临时资源，再交由浏览器加载。', 24, 94, 15, '#2f3a4f');
    text(ctx, '复制动作可通过浏览器选区和复制命令完成。', 24, 122, 15, '#2f3a4f');
  }

  function drawLayerChart(ctx, W, H) {
    ctx.fillStyle = '#fafafa';
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    text(ctx, '聊天层和生产层的差别', 28, 36, 14, '#333', 'bold');
    const left = ctx.createLinearGradient(48, 74, 264, 174);
    left.addColorStop(0, '#eef2ff');
    left.addColorStop(1, '#f5f7ff');
    ctx.fillStyle = left;
    roundRect(ctx, 48, 74, 230, 104, 14);
    ctx.fill();
    ctx.strokeStyle = '#c7d2fe';
    ctx.stroke();
    text(ctx, '聊天层', 163, 112, 20, '#4f46e5', 'bold', 'center');
    text(ctx, '回答、解释、建议', 163, 146, 14, '#667085', '', 'center');
    ctx.fillStyle = '#fff';
    roundRect(ctx, 382, 74, 230, 104, 14);
    ctx.fill();
    ctx.strokeStyle = '#d7dcea';
    ctx.stroke();
    text(ctx, '生产层', 497, 112, 20, '#1f2937', 'bold', 'center');
    text(ctx, '文件、预览、验证', 497, 146, 14, '#667085', '', 'center');
    ctx.strokeStyle = '#667eea';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(292, 126);
    ctx.lineTo(366, 126);
    ctx.stroke();
    ctx.fillStyle = '#667eea';
    ctx.beginPath();
    ctx.moveTo(366, 126);
    ctx.lineTo(348, 116);
    ctx.lineTo(348, 136);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = '#f0f5ff';
    roundRect(ctx, 80, 218, 500, 42, 21);
    ctx.fill();
    text(ctx, '能交付、能打开、能修复，才算进入真实工作流', 330, 245, 15, '#3b4260', '', 'center');
  }

  function drawPipelineChart(ctx, W, H) {
    ctx.fillStyle = '#fafafa';
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    text(ctx, '公众号 HTML 的绘图和复制链路', 28, 36, 14, '#333', 'bold');
    const labels = ['Canvas绘制', '转成PNG', '写入img', '复制正文'];
    labels.forEach((label, i) => {
      const x = 44 + i * 154;
      ctx.fillStyle = i % 2 === 0 ? '#eef2ff' : '#f0f9ff';
      roundRect(ctx, x, 92, 118, 62, 12);
      ctx.fill();
      ctx.strokeStyle = i % 2 === 0 ? '#c7d2fe' : '#bae6fd';
      ctx.stroke();
      text(ctx, label, x + 59, 130, 15, i % 2 === 0 ? '#3730a3' : '#075985', 'bold', 'center');
      if (i < labels.length - 1) {
        ctx.strokeStyle = '#667eea';
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(x + 128, 123);
        ctx.lineTo(x + 146, 123);
        ctx.stroke();
        ctx.fillStyle = '#667eea';
        ctx.beginPath();
        ctx.moveTo(x + 146, 123);
        ctx.lineTo(x + 134, 116);
        ctx.lineTo(x + 134, 130);
        ctx.closePath();
        ctx.fill();
      }
    });
    ctx.fillStyle = '#f8fbff';
    roundRect(ctx, 72, 204, 516, 52, 26);
    ctx.fill();
    text(ctx, '最终复制的是图片和正文，不是容易丢失的CSS背景块', 330, 237, 15, '#667085', '', 'center');
  }

  function drawCapabilityChart(ctx, W, H) {
    ctx.fillStyle = '#fafafa';
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    text(ctx, '判断运行时真假的三张账', 28, 36, 14, '#333', 'bold');
    const cards = [
      ['格式账', '能不能产出真实文件'],
      ['验证账', '能不能打开渲染检查'],
      ['迭代账', '缺什么能不能补什么']
    ];
    cards.forEach((card, i) => {
      const x = 48 + i * 204;
      const g = ctx.createLinearGradient(x, 78, x + 160, 190);
      g.addColorStop(0, i === 0 ? '#eef2ff' : i === 1 ? '#ecfeff' : '#f5f3ff');
      g.addColorStop(1, '#ffffff');
      ctx.fillStyle = g;
      roundRect(ctx, x, 82, 160, 136, 16);
      ctx.fill();
      ctx.strokeStyle = '#d7dcea';
      ctx.stroke();
      text(ctx, card[0], x + 80, 128, 22, '#4f46e5', 'bold', 'center');
      ctx.fillStyle = '#edf2ff';
      ctx.beginPath();
      ctx.arc(x + 80, 164, 18, 0, Math.PI * 2);
      ctx.fill();
      text(ctx, String(i + 1), x + 80, 171, 16, '#667eea', 'bold', 'center');
      text(ctx, card[1], x + 80, 198, 13, '#667085', '', 'center');
    });
  }

  function drawStepCard(ctx, W, H, num, title, desc) {
    ctx.fillStyle = '#f8f9fa';
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.fill();
    ctx.strokeStyle = '#e8e8e8';
    ctx.lineWidth = 1;
    roundRect(ctx, 0, 0, W, H, 12);
    ctx.stroke();
    ctx.fillStyle = '#1890ff';
    ctx.beginPath();
    ctx.arc(36, 38, 18, 0, Math.PI * 2);
    ctx.fill();
    text(ctx, num, 36, 44, 16, '#fff', 'bold', 'center');
    text(ctx, title, 66, 31, 16, '#1a1a1a', 'bold');
    text(ctx, desc, 66, 58, 14, '#666');
  }

  function drawCta(ctx, W, H) {
    const g = ctx.createLinearGradient(0, 0, W, H);
    g.addColorStop(0, '#667eea');
    g.addColorStop(1, '#764ba2');
    ctx.fillStyle = g;
    roundRect(ctx, 0, 0, W, H, 14);
    ctx.fill();
    text(ctx, '下一步，不要只测聊天。', W / 2, 42, 20, '#fff', 'bold', 'center');
    text(ctx, '测文件、测预览、测修复，才知道运行时是不是真的。', W / 2, 78, 15, '#fff', '', 'center');
  }

  function drawAuthorCard(ctx, W, H) {
    const bg = ctx.createLinearGradient(0, 0, W, H);
    bg.addColorStop(0, '#ffffff');
    bg.addColorStop(1, '#f5f7fa');
    ctx.fillStyle = bg;
    roundRect(ctx, 0, 0, W, H, 16);
    ctx.fill();
    ctx.strokeStyle = '#e6eaf2';
    ctx.lineWidth = 1;
    roundRect(ctx, 0, 0, W, H, 16);
    ctx.stroke();

    const bar = ctx.createLinearGradient(0, 0, 0, H);
    bar.addColorStop(0, '#667eea');
    bar.addColorStop(1, '#764ba2');
    ctx.fillStyle = bar;
    roundRect(ctx, 0, 0, 6, H, 3);
    ctx.fill();

    const ring = ctx.createLinearGradient(34, 28, 110, 104);
    ring.addColorStop(0, '#667eea');
    ring.addColorStop(1, '#764ba2');
    ctx.fillStyle = ring;
    ctx.beginPath();
    ctx.arc(74, 66, 38, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(74, 66, 30, 0, Math.PI * 2);
    ctx.fill();
    text(ctx, '詹', 74, 76, 28, '#667eea', 'bold', 'center');
    text(ctx, '詹老师', 74, 126, 18, '#1f2430', 'bold', 'center');
    text(ctx, '📱 13136092523', 74, 152, 14, '#667eea', '', 'center');
    text(ctx, '微信同号', 74, 174, 13, '#8a92a6', '', 'center');

    ctx.fillStyle = '#eef2ff';
    roundRect(ctx, 188, 22, 438, 28, 14);
    ctx.fill();
    text(ctx, 'AI流程管理概念倡导者 · 面向流程体系构建AI能力提出者', 202, 41, 13, '#5d63c8', 'bold');
    text(ctx, '核心实践', 188, 78, 15, '#1f2430', 'bold');
    text(ctx, '● 搭建国内首个面向AI流程体系的产品与智能体', 188, 104, 14, '#4f5665');
    text(ctx, '● 主导多个AI智能体编排平台、Skill平台', 188, 128, 14, '#4f5665');
    text(ctx, '  及专业插件设计', 188, 150, 14, '#4f5665');
    ctx.strokeStyle = '#e0e5ef';
    ctx.beginPath(); ctx.moveTo(188, 160); ctx.lineTo(626, 160); ctx.stroke();
    const tag = ctx.createLinearGradient(188, 168, 382, 168);
    tag.addColorStop(0, '#667eea');
    tag.addColorStop(1, '#764ba2');
    ctx.fillStyle = tag;
    roundRect(ctx, 188, 168, 88, 22, 11); ctx.fill();
    roundRect(ctx, 286, 168, 96, 22, 11); ctx.fill();
    text(ctx, 'AI流程管理', 232, 184, 12, '#fff', '', 'center');
    text(ctx, 'AI产品设计', 334, 184, 12, '#fff', '', 'center');
    text(ctx, '聊流程、聊AI产品设计，随时找到我', 626, 184, 12, '#98a1b3', '', 'right');
  }

  function mergeStyle(node, styleText) {
    const existing = node.getAttribute('style') || '';
    node.setAttribute('style', existing ? existing + ';' + styleText : styleText);
  }

  function buildWechatArticleHtml() {
    const source = document.getElementById('article-content');
    const clone = source.cloneNode(true);
    mergeStyle(clone, "max-width:677px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue','Microsoft YaHei',sans-serif;font-size:15px;line-height:1.86;color:#263041;background:#ffffff;");

    clone.querySelectorAll('*').forEach(node => {
      node.removeAttribute('id');
      node.removeAttribute('class');
    });

    clone.querySelectorAll('h1').forEach(node => mergeStyle(node, 'font-size:24px;line-height:1.35;font-weight:850;color:#111827;margin:24px 0 12px;padding:0;'));
    clone.querySelectorAll('h2').forEach(node => mergeStyle(node, 'font-size:18px;line-height:1.45;font-weight:850;color:#111827;margin:34px 0 14px;padding-left:12px;border-left:4px solid #2563eb;'));
    clone.querySelectorAll('p').forEach(node => mergeStyle(node, 'font-size:15px;line-height:1.86;color:#263041;margin:0 0 16px;padding:0;'));
    clone.querySelectorAll('strong').forEach(node => mergeStyle(node, 'font-weight:850;color:#0f172a;'));
    clone.querySelectorAll('a').forEach(node => mergeStyle(node, 'color:#2563eb;text-decoration:none;border-bottom:1px solid #bfdbfe;'));
    clone.querySelectorAll('img').forEach(node => mergeStyle(node, 'width:100%;max-width:100%;height:auto;display:block;border:0;margin:22px 0;'));

    const paragraphs = Array.from(clone.querySelectorAll('p'));
    if (paragraphs[0]) mergeStyle(paragraphs[0], 'color:#758195;font-size:13px;line-height:1.6;margin:10px 0 24px;');
    paragraphs.forEach(p => {
      if (p.textContent.trim().startsWith('图') || p.textContent.includes('核查依据')) {
        mergeStyle(p, 'font-size:13px;color:#8a94a6;line-height:1.6;margin:10px 0 0;text-align:center;');
      }
    });

    Array.from(clone.querySelectorAll('div')).forEach(node => {
      if (node.querySelector('img')) {
        mergeStyle(node, 'margin:30px 0;text-align:center;');
      }
    });

    return '<section style="max-width:677px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,\\'PingFang SC\\',\\'Helvetica Neue\\',\\'Microsoft YaHei\\',sans-serif;font-size:15px;line-height:1.86;color:#263041;background:#ffffff;">' + clone.innerHTML + '</section>';
  }

  function copyHtmlWithSelection(html) {
    const holder = document.createElement('div');
    holder.setAttribute('contenteditable', 'true');
    holder.style.position = 'fixed';
    holder.style.left = '-9999px';
    holder.style.top = '0';
    holder.innerHTML = html;
    document.body.appendChild(holder);
    const range = document.createRange();
    range.selectNodeContents(holder);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    document.execCommand('copy');
    sel.removeAllRanges();
    holder.remove();
  }

  async function copyArticle() {
    const html = buildWechatArticleHtml();
    const text = document.getElementById('article-content').innerText;
    try {
      if (navigator.clipboard && window.ClipboardItem) {
        await navigator.clipboard.write([new ClipboardItem({
          'text/html': new Blob([html], { type: 'text/html' }),
          'text/plain': new Blob([text], { type: 'text/plain' })
        })]);
      } else {
        copyHtmlWithSelection(html);
      }
      const toast = document.getElementById('toast');
      toast.classList.add('show');
      setTimeout(() => toast.classList.remove('show'), 2500);
    } catch(e) {
      try {
        copyHtmlWithSelection(html);
      } catch(fallbackError) {
        alert('请手动 Ctrl+A 全选后复制');
      }
    }
  }

  window.addEventListener('DOMContentLoaded', function () {
    canvasToPng(drawHeader, 'header-img', 660, 270);
    canvasToPng((ctx, W, H) => drawGold(ctx, W, H, (ARTICLE_VISUALS.goldLines || []).slice(0, 2)), 'gold-1', 660, 92);
    canvasToPng(drawLayerChart, 'chart1', 660, 292);
    canvasToPng(drawBlueBox, 'box-info', 660, 146);
    canvasToPng(drawPipelineChart, 'chart2', 660, 286);
    const actions = ARTICLE_VISUALS.actionList || [];
    canvasToPng((ctx, W, H) => drawStepCard(ctx, W, H, '1', '第一步', actions[0] || '选一个高频流程，写清楚输入和输出。'), 'step-1', 660, 84);
    canvasToPng((ctx, W, H) => drawStepCard(ctx, W, H, '2', '第二步', actions[1] || '把流程规则沉淀成可复用 Skill。'), 'step-2', 660, 84);
    canvasToPng((ctx, W, H) => drawStepCard(ctx, W, H, '3', '第三步', actions[2] || '让运行时生成真实交付物。'), 'step-3', 660, 84);
    canvasToPng((ctx, W, H) => drawStepCard(ctx, W, H, '4', '第四步', actions[3] || '用验收标准检查结果。'), 'step-4', 660, 84);
    canvasToPng(drawCapabilityChart, 'chart3', 660, 276);
    canvasToPng((ctx, W, H) => drawGold(ctx, W, H, (ARTICLE_VISUALS.goldLines || []).slice(2, 4).length ? (ARTICLE_VISUALS.goldLines || []).slice(2, 4) : (ARTICLE_VISUALS.goldLines || []).slice(0, 2)), 'gold-2', 660, 116);
    canvasToPng(drawCta, 'cta-img', 660, 112);
    canvasToPng(drawAuthorCard, 'author-card', 660, 190);
  });
</script>
</body>
</html>
`;

await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(outputPath, html, "utf8");
console.log(outputPath);
