/* Divination flow:
 *  1. user fills form → submit
 *  2. modal collapses, compass bursts into fast spin (decaying ~30s)
 *  3. AI request runs in parallel
 *  4. when both timer & AI done → modal expands with structured report
 *  5. report saveable as a vertical poster image (mobile share)
 */

(function () {
  const { useState, useEffect, useRef } = React;

  const CF_MODEL = "@cf/qwen/qwen1.5-14b-chat-awq";
  const SPIN_MS = 10000;

  /* ── 会话 & 支付 ── */
  const SESSION_KEY = 'fortune_session';   // { token, email, usesRemaining }
  const PAID_USES   = 5;                   // 每次购买获得的次数
  function getSession()   { try { return JSON.parse(localStorage.getItem(SESSION_KEY) || 'null'); } catch { return null; } }
  function saveSession(s) { localStorage.setItem(SESSION_KEY, JSON.stringify(s)); }
  function clearSession() { localStorage.removeItem(SESSION_KEY); }

  // Common countries (ISO-ish list) — labels use current i18n via getLang
  const COUNTRIES = [
    { code: "CN", zh: "中国", en: "China",         es: "China",          ja: "中国" },
    { code: "HK", zh: "中国香港", en: "Hong Kong",  es: "Hong Kong",      ja: "香港" },
    { code: "TW", zh: "中国台湾", en: "Taiwan",     es: "Taiwán",         ja: "台湾" },
    { code: "US", zh: "美国",  en: "United States", es: "Estados Unidos", ja: "アメリカ" },
    { code: "GB", zh: "英国",  en: "United Kingdom",es: "Reino Unido",    ja: "イギリス" },
    { code: "JP", zh: "日本",  en: "Japan",         es: "Japón",          ja: "日本" },
    { code: "KR", zh: "韩国",  en: "South Korea",   es: "Corea del Sur",  ja: "韓国" },
    { code: "SG", zh: "新加坡",en: "Singapore",     es: "Singapur",       ja: "シンガポール" },
    { code: "MY", zh: "马来西亚",en:"Malaysia",     es: "Malasia",        ja: "マレーシア" },
    { code: "TH", zh: "泰国",  en: "Thailand",      es: "Tailandia",      ja: "タイ" },
    { code: "VN", zh: "越南",  en: "Vietnam",       es: "Vietnam",        ja: "ベトナム" },
    { code: "IN", zh: "印度",  en: "India",         es: "India",          ja: "インド" },
    { code: "AU", zh: "澳大利亚",en:"Australia",    es: "Australia",      ja: "オーストラリア" },
    { code: "NZ", zh: "新西兰",en: "New Zealand",   es: "Nueva Zelanda",  ja: "ニュージーランド" },
    { code: "CA", zh: "加拿大",en: "Canada",        es: "Canadá",         ja: "カナダ" },
    { code: "DE", zh: "德国",  en: "Germany",       es: "Alemania",       ja: "ドイツ" },
    { code: "FR", zh: "法国",  en: "France",        es: "Francia",        ja: "フランス" },
    { code: "IT", zh: "意大利",en: "Italy",         es: "Italia",         ja: "イタリア" },
    { code: "ES", zh: "西班牙",en: "Spain",         es: "España",         ja: "スペイン" },
    { code: "PT", zh: "葡萄牙",en: "Portugal",      es: "Portugal",       ja: "ポルトガル" },
    { code: "NL", zh: "荷兰",  en: "Netherlands",   es: "Países Bajos",   ja: "オランダ" },
    { code: "SE", zh: "瑞典",  en: "Sweden",        es: "Suecia",         ja: "スウェーデン" },
    { code: "CH", zh: "瑞士",  en: "Switzerland",   es: "Suiza",          ja: "スイス" },
    { code: "RU", zh: "俄罗斯",en: "Russia",        es: "Rusia",          ja: "ロシア" },
    { code: "BR", zh: "巴西",  en: "Brazil",        es: "Brasil",         ja: "ブラジル" },
    { code: "MX", zh: "墨西哥",en: "Mexico",        es: "México",         ja: "メキシコ" },
    { code: "AR", zh: "阿根廷",en: "Argentina",     es: "Argentina",      ja: "アルゼンチン" },
    { code: "AE", zh: "阿联酋",en: "UAE",           es: "EAU",            ja: "UAE" },
    { code: "SA", zh: "沙特",  en: "Saudi Arabia",  es: "Arabia Saudí",   ja: "サウジアラビア" },
    { code: "ZA", zh: "南非",  en: "South Africa",  es: "Sudáfrica",      ja: "南アフリカ" },
    { code: "OTHER", zh: "其他",en: "Other",        es: "Otro",           ja: "その他" },
  ];

  const COMPASS_CONTEXT = `本罗盘共六层：
1) 太极 阴阳
2) 八卦：乾(天) 兑(泽) 离(火) 震(雷) 巽(风) 坎(水) 艮(山) 坤(地)
3) 十二地支：子丑寅卯辰巳午未申酉戌亥
4) 二十四节气：立春雨水惊蛰春分清明谷雨立夏小满芒种夏至小暑大暑立秋处暑白露秋分寒露霜降立冬小雪大雪冬至小寒大寒
5) 二十四山：壬子癸丑艮寅甲卯乙辰巽巳丙午丁未坤申庚酉辛戌乾亥
6) 六十四卦：乾坤屯蒙需讼师比小畜履泰否同人大有谦豫随蛊临观噬嗑贲剥复无妄大畜颐大过坎离咸恒遁大壮晋明夷家人睽蹇解损益夬姤萃升困井革鼎震艮渐归妹丰旅巽兑涣节中孚小过既济未济`;

  const ZODIAC = ["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"];
  const STEMS  = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"];
  const BRANCH = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"];
  const t = (k) => (window.I18N ? window.I18N.t(k) : k);
  const getLang = () => (window.I18N ? window.I18N.getLang() : 'zh');
  function useLangBump() {
    const [, f] = React.useReducer(x => x + 1, 0);
    React.useEffect(() => {
      const h = () => f();
      window.addEventListener('langchange', h);
      return () => window.removeEventListener('langchange', h);
    }, []);
  }

  function calcInfo(birthday) {
    if (!birthday) return null;
    const d = new Date(birthday);
    if (isNaN(d)) return null;
    const y = d.getFullYear();
    const stemIdx = ((y - 1984) % 10 + 10) % 10;
    const branchIdx = ((y - 1984) % 12 + 12) % 12;
    return {
      year: y, zodiac: ZODIAC[branchIdx],
      ganzhi: STEMS[stemIdx] + BRANCH[branchIdx],
      month: d.getMonth() + 1, day: d.getDate(),
    };
  }

  function buildPrompt(form) {
    const info = calcInfo(form.birthday);
    const meta = info
      ? `生年：${info.year}（${info.ganzhi}年，属${info.zodiac}），公历：${info.year}-${String(info.month).padStart(2,"0")}-${String(info.day).padStart(2,"0")}`
      : `生日：${form.birthday || "未填写"}`;

    const lang = getLang();
    const titles = (window.I18N ? window.I18N.t('sec_titles') : null) || ['1','2','3','4','5','6','7'];
    const langInstr = {
      zh: '用简洁的现代白话文写作，不用文言文，普通人能轻松读懂。语气温和、亲切，像朋友聊天一样自然。',
      en: 'Write in clear, warm, everyday English. No markdown. Speak like a thoughtful friend.',
      es: 'Escribe en español claro y cotidiano. Sin markdown. Como un amigo reflexivo.',
      ja: '分かりやすい現代語で、親しみやすい口調で書いてください。マークダウンは使わない。',
    }[lang] || 'Write in clear, warm, everyday English. No markdown.';

    return `You are a thoughtful guide framing the Chinese I-Ching as a tool for self-reflection, mindfulness and cultural appreciation — NOT as fortune-telling, prediction, or advice. Avoid medical, legal, financial, or definite future claims. Speak in metaphor; invite the reader to reflect.

Reference compass (cultural symbols only):
${COMPASS_CONTEXT}

Reader:
Country: ${(COUNTRIES.find(c => c.code === form.country) || {}).en || form.country || "-"}
Name: ${form.name || "-"}
Gender: ${form.gender}
${meta}

${langInstr}
For each of the seven sections below, write 2–3 short reflective sentences, addressing the reader by their name where natural. Use this EXACT format with bracketed titles, in the requested language:

【${titles[0]}】
( a gentle observation about the reader's present state, framed as imagery )

【${titles[1]}】
( pick ONE hexagram from the 64 as a metaphor for self-reflection, name it, and give its image meaning — not a prediction )

【${titles[2]}】
( the reader's elemental tendency among Wood/Fire/Earth/Metal/Water as a personality lens )

【${titles[3]}】
( one of the 24 solar terms, used as a seasonal mood metaphor for inner life )

【${titles[4]}】
( a direction from the eight trigrams, framed as a symbolic orientation for attention — not a place to go )

【${titles[5]}】
( a short mindfulness invitation: a breath, a question, a thing to notice this week )

【${titles[6]}】
( two or three gentle, non-prescriptive practices: journaling prompt, breathing, observation — never medical )

End by softly reminding the reader this is a cultural reflection, not a prediction. Output the seven bracketed sections only.`;
  }

  function parseReport(text) {
    if (!text) return [];
    const out = [];
    const re = /【([^】]+)】\s*([\s\S]*?)(?=【|$)/g;
    let m;
    while ((m = re.exec(text)) !== null) {
      out.push({ title: m[1].trim(), body: m[2].trim() });
    }
    if (out.length === 0) {
      // fallback: split by 一二三...
      const lines = text.split(/\n+/).filter(Boolean);
      return [{ title: t('report_title'), body: lines.join("\n") }];
    }
    return out;
  }

  function getCfg(){ try{return JSON.parse(localStorage.getItem("cf_cfg")||"{}");}catch{return{};} }
  function setCfg(c){ localStorage.setItem("cf_cfg", JSON.stringify(c)); }

  const CF_MESSAGES_WRAPPER = (prompt) => ({
    messages: [
      { role: "system", content: "You are a thoughtful cultural guide who frames the I-Ching as a mindfulness and self-reflection tool, never as fortune-telling. You avoid medical, legal, financial, or predictive claims and invite gentle reflection." },
      { role: "user", content: prompt },
    ],
    max_tokens: 1500,
  });

  function extractResult(data) {
    if (!data.success) throw new Error((data.errors || []).map(e => e.message).join("; ") || "AI 请求失败");
    const r = data.result;
    // OpenAI-compatible format（新模型：choices[0].message.content）
    const text =
      r?.choices?.[0]?.message?.content ??
      r?.response ??
      r?.output ??
      (typeof r === "string" ? r : JSON.stringify(r));
    // 把模型输出的字面量 \n（反斜杠+n）转成真正的换行符
    return String(text).replace(/\\n/g, "\n").trim();
  }

  /** 优先：通过 Pages Function 代理（需带 session token）
   *  返回 { text, usesRemaining }
   */
  async function callProxy(prompt, sessionToken) {
    const headers = { "Content-Type": "application/json" };
    if (sessionToken) headers["Authorization"] = `Bearer ${sessionToken}`;
    const resp = await fetch("/api/ai", {
      method: "POST", headers,
      body: JSON.stringify(CF_MESSAGES_WRAPPER(prompt)),
    });
    const data = await resp.json();
    // Auth / usage 错误码直接上抛
    if (data.code === 'NOT_LOGGED_IN') throw new Error('NOT_LOGGED_IN');
    if (data.code === 'SESSION_EXPIRED') throw new Error('SESSION_EXPIRED');
    if (data.code === 'NO_USES') throw new Error('NO_USES');
    return { text: extractResult(data), usesRemaining: data.usesRemaining };
  }

  /** 降级：用户在 ⚙ 中手动填写的凭证直连 Cloudflare API */
  async function callCloudflare(prompt, cfg) {
    const url = `https://api.cloudflare.com/client/v4/accounts/${cfg.accountId}/ai/run/${cfg.model || CF_MODEL}`;
    const resp = await fetch(url, {
      method: "POST",
      headers: { "Authorization": `Bearer ${cfg.token}`, "Content-Type": "application/json" },
      body: JSON.stringify(CF_MESSAGES_WRAPPER(prompt)),
    });
    if (!resp.ok) {
      const t = await resp.text();
      throw new Error(`Cloudflare API ${resp.status}: ${t.slice(0,200)}`);
    }
    const data = await resp.json();
    return { text: extractResult(data) };
  }

  /** 最后降级：Claude Code 内置预览环境 */
  async function callClaude(prompt) {
    if (!window.claude || typeof window.claude.complete !== "function") {
      throw new Error("未检测到 AI 接口，请先登录后使用。");
    }
    return { text: await window.claude.complete(prompt) };
  }

  /** 统一入口：代理 → 客户端配置 → window.claude
   *  返回 { text, usesRemaining? }
   */
  async function callAI(prompt, provider, cfg, sessionToken) {
    if (provider !== "claude") {
      try {
        return await callProxy(prompt, sessionToken);
      } catch (e) {
        if (['NOT_LOGGED_IN','SESSION_EXPIRED','NO_USES'].includes(e.message)) throw e;
        if (!e.message.includes("未配置") && !String(e).includes("503") && !String(e).includes("fetch")) throw e;
      }
    }
    if ((provider === "cloudflare" || provider === "auto") && cfg.accountId && cfg.token) {
      return await callCloudflare(prompt, cfg);
    }
    return await callClaude(prompt);
  }

  /* ========== Poster generator ========== */
  function wrapText(ctx, text, x, y, maxW, lineHeight) {
    const chars = [...text];
    let line = "";
    let curY = y;
    for (let i = 0; i < chars.length; i++) {
      const test = line + chars[i];
      if (ctx.measureText(test).width > maxW && line) {
        ctx.fillText(line, x, curY);
        line = chars[i];
        curY += lineHeight;
      } else {
        line = test;
      }
    }
    if (line) { ctx.fillText(line, x, curY); curY += lineHeight; }
    return curY;
  }

  function generatePoster(form, sections) {
    const W = 750;
    const PAD_L = 70;
    const MAX_TW = W - 140;
    const BODY_LH = 36;   // line height for body text
    const SITE_URL = "fate.fw39.com";
    const DISCLAIMER = "本报告为易经文化意象参考，非命理预测或专业建议，仅供自我反思之用。";

    // ── Pass 1: measure actual content height ──────────────────────────────
    // Use a temporary canvas so measureText works with correct font metrics
    const tmp = document.createElement("canvas");
    tmp.width = W;
    const mCtx = tmp.getContext("2d");

    function measureWrappedLines(ctx, text, maxW, font) {
      ctx.font = font;
      const chars = [...text];
      let line = "", lines = 0;
      for (const ch of chars) {
        const test = line + ch;
        if (ctx.measureText(test).width > maxW && line) { lines++; line = ch; }
        else { line = test; }
      }
      if (line) lines++;
      return lines;
    }

    // Fixed areas above sections
    //   header title+sub: 0→140
    //   bagua circle: 140→385 (cy=260, R=100 + gap)
    //   user info card: 385→540
    let estimatedH = 540;

    sections.forEach(s => {
      estimatedH += 64; // section title bar + padding
      // body: split by real newlines, wrap each paragraph
      const paras = s.body.split("\n").filter(p => p.trim());
      paras.forEach(p => {
        const lines = measureWrappedLines(mCtx, p.trim(), MAX_TW, `17px 'Noto Serif SC', serif`);
        estimatedH += lines * BODY_LH;
      });
      estimatedH += 32; // gap between sections
    });

    estimatedH += 10 + 90;   // stamp (80) + gap
    estimatedH += 30 + 22 + 22 + 22; // disclaimer + url + date + poem
    estimatedH += 60;         // bottom border padding

    const H = Math.max(estimatedH, 900);

    // ── Pass 2: draw ───────────────────────────────────────────────────────
    const c = document.createElement("canvas");
    c.width = W; c.height = H;
    const ctx = c.getContext("2d");

    // Background gradient
    const g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, "#1a0a26");
    g.addColorStop(0.4, "#0a0418");
    g.addColorStop(1, "#1a0810");
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // Nebula glows
    const grad1 = ctx.createRadialGradient(W*0.2, H*0.15, 0, W*0.2, H*0.15, 400);
    grad1.addColorStop(0, "rgba(140,70,200,0.35)");
    grad1.addColorStop(1, "rgba(140,70,200,0)");
    ctx.fillStyle = grad1; ctx.fillRect(0, 0, W, H);
    const grad2 = ctx.createRadialGradient(W*0.85, H*0.6, 0, W*0.85, H*0.6, 500);
    grad2.addColorStop(0, "rgba(40,130,200,0.25)");
    grad2.addColorStop(1, "rgba(40,130,200,0)");
    ctx.fillStyle = grad2; ctx.fillRect(0, 0, W, H);

    // Stars (deterministic seed via section count so same poster = same stars)
    let rng = sections.length * 1234567;
    const rand = () => { rng = (rng * 1664525 + 1013904223) & 0xffffffff; return (rng >>> 0) / 0xffffffff; };
    for (let i = 0; i < 80; i++) {
      const sx = rand() * W, sy = rand() * H, sr = rand() * 1.4 + 0.3;
      ctx.globalAlpha = 0.3 + rand() * 0.6;
      ctx.fillStyle = "#fff";
      ctx.beginPath(); ctx.arc(sx, sy, sr, 0, Math.PI*2); ctx.fill();
    }
    ctx.globalAlpha = 1;

    // Border
    ctx.strokeStyle = "rgba(255,206,92,0.5)"; ctx.lineWidth = 1.5;
    ctx.strokeRect(20, 20, W-40, H-40);
    ctx.strokeStyle = "rgba(255,206,92,0.25)"; ctx.lineWidth = 1;
    ctx.strokeRect(28, 28, W-56, H-56);

    // Title
    ctx.textAlign = "center";
    ctx.fillStyle = "#ffe9a8";
    ctx.font = "700 50px 'Ma Shan Zheng','STKaiti',serif";
    ctx.shadowColor = "rgba(255,206,92,0.7)"; ctx.shadowBlur = 24;
    ctx.fillText("玄 机 罗 盘", W/2, 100);
    ctx.shadowBlur = 0;
    ctx.font = "400 16px 'Noto Serif SC',serif";
    ctx.fillStyle = "rgba(255,233,168,0.6)";
    ctx.fillText("心 象 报 告", W/2, 130);

    // Bagua circle
    const bx = W/2, by = 260, R = 100;
    ctx.strokeStyle = "rgba(255,206,92,0.7)"; ctx.lineWidth = 1.2;
    [R+10, R, R-25, R-50, R-75].forEach(rr => {
      ctx.beginPath(); ctx.arc(bx, by, rr, 0, Math.PI*2); ctx.stroke();
    });
    const trigs = ["☰","☱","☲","☳","☴","☵","☶","☷"];
    ctx.fillStyle = "#ffce5c"; ctx.font = "20px serif";
    trigs.forEach((tg, i) => {
      const a = (i/8)*Math.PI*2 - Math.PI/2;
      ctx.fillText(tg, bx+(R-12)*Math.cos(a), by+(R-12)*Math.sin(a)+7);
    });
    ctx.fillStyle = "#ffe9a8";
    ctx.beginPath(); ctx.arc(bx, by, 22, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = "#1a0810";
    ctx.beginPath();
    ctx.arc(bx, by, 22, -Math.PI/2, Math.PI/2);
    ctx.arc(bx, by+11, 11, Math.PI/2, -Math.PI/2, true);
    ctx.arc(bx, by-11, 11, Math.PI/2, -Math.PI/2);
    ctx.closePath(); ctx.fill();
    ctx.fillStyle = "#1a0810"; ctx.beginPath(); ctx.arc(bx, by-11, 3, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = "#ffe9a8"; ctx.beginPath(); ctx.arc(bx, by+11, 3, 0, Math.PI*2); ctx.fill();

    // User info card
    let y = 385;
    ctx.fillStyle = "rgba(255,206,92,0.08)";
    ctx.fillRect(50, y, W-100, 115);
    ctx.strokeStyle = "rgba(255,206,92,0.3)"; ctx.lineWidth = 1;
    ctx.strokeRect(50, y, W-100, 115);
    y += 38;
    ctx.textAlign = "left";
    ctx.fillStyle = "#ffe9a8";
    ctx.font = "600 26px 'Ma Shan Zheng','STKaiti',serif";
    ctx.fillText(form.name || "无名氏", PAD_L, y);
    const info = calcInfo(form.birthday);
    ctx.font = "14px 'Noto Serif SC',serif";
    ctx.fillStyle = "rgba(255,233,168,0.7)";
    ctx.fillText(`${form.country||""}　·　${form.gender==="M"?"男":"女"}　·　${form.birthday||""}`, PAD_L, y+28);
    if (info) {
      ctx.fillStyle = "#ffce5c"; ctx.font = "16px 'Ma Shan Zheng',serif";
      ctx.fillText(`${info.ganzhi}年　·　属 ${info.zodiac}`, PAD_L, y+56);
    }
    y = 385 + 115 + 20;

    // Sections
    sections.forEach(s => {
      // Left accent bar + title (title already contains ordinal, e.g. "一 · 处境之象")
      ctx.fillStyle = "rgba(255,206,92,0.9)";
      ctx.fillRect(50, y + 2, 4, 26);
      ctx.fillStyle = "#ffce5c";
      ctx.font = "600 20px 'Ma Shan Zheng','STKaiti',serif";
      ctx.textAlign = "left";
      ctx.shadowColor = "rgba(255,206,92,0.4)"; ctx.shadowBlur = 8;
      ctx.fillText(s.title, PAD_L, y + 26);
      ctx.shadowBlur = 0;
      // section title underline
      ctx.strokeStyle = "rgba(255,206,92,0.2)"; ctx.lineWidth = 0.8;
      ctx.beginPath(); ctx.moveTo(50, y + 38); ctx.lineTo(W - 50, y + 38); ctx.stroke();
      y += 54;

      // Body — preserve paragraphs (split by \n)
      ctx.fillStyle = "rgba(255,233,168,0.88)";
      ctx.font = "17px 'Noto Serif SC',serif";
      const paras = s.body.split("\n").filter(p => p.trim());
      paras.forEach(p => {
        y = wrapText(ctx, p.trim(), PAD_L, y, MAX_TW, BODY_LH);
      });
      y += 30; // gap between sections
    });

    // Stamp
    y += 14;
    ctx.fillStyle = "rgba(165,42,20,0.9)";
    ctx.fillRect(W/2-38, y, 76, 76);
    ctx.strokeStyle = "rgba(255,206,92,0.6)"; ctx.lineWidth = 1.5;
    ctx.strokeRect(W/2-38, y, 76, 76);
    ctx.fillStyle = "#fff5d6";
    ctx.font = "600 36px 'Ma Shan Zheng',serif";
    ctx.textAlign = "center";
    ctx.fillText("易", W/2, y + 52);
    y += 96;

    // Divider
    ctx.strokeStyle = "rgba(255,206,92,0.3)"; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(60, y); ctx.lineTo(W-60, y); ctx.stroke();
    y += 24;

    // Disclaimer
    ctx.fillStyle = "rgba(255,233,168,0.45)";
    ctx.font = "12px 'Noto Serif SC',serif";
    ctx.textAlign = "center";
    ctx.fillText(DISCLAIMER, W/2, y);
    y += 22;

    // Website URL
    ctx.fillStyle = "rgba(255,206,92,0.7)";
    ctx.font = "13px 'Noto Serif SC',serif";
    ctx.fillText(SITE_URL, W/2, y);
    y += 22;

    // Date
    ctx.fillStyle = "rgba(255,233,168,0.4)";
    ctx.font = "12px 'Noto Serif SC',serif";
    ctx.fillText(new Date().toLocaleDateString("zh-CN", { year:"numeric", month:"long", day:"numeric" }), W/2, y);

    return c;
  }

  async function savePoster(canvas, name) {
    const blob = await new Promise(res => canvas.toBlob(res, "image/png"));
    const filename = `${name || "reflection"}_iching.png`;
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = filename;
    document.body.appendChild(a); a.click();
    document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  /* ========== DateField — Y/M/D dropdowns ========== */
  function DateField({ label, value, onChange, optional }) {
    // Parse a "YYYY-MM-DD" string into [year, month, day] strings whose values
    // match the <option value={n}> attributes (plain integers, no leading zeros).
    // e.g. "1990-03-05" → ["1990", "3", "5"]  so React can match the options.
    const parseDate = (v) => {
      if (!v) return ["", "", ""];
      const p = v.split("-");
      return [
        p[0] || "",
        p[1] ? String(parseInt(p[1], 10)) : "",
        p[2] ? String(parseInt(p[2], 10)) : "",
      ];
    };
    const parsed = parseDate(value);
    const [y, setY] = useState(parsed[0]);
    const [m, setM] = useState(parsed[1]);
    const [d, setD] = useState(parsed[2]);
    React.useEffect(() => {
      const [py, pm, pd] = parseDate(value);
      setY(py); setM(pm); setD(pd);
    }, [value]);
    const update = (yy, mm, dd) => {
      if (yy && mm && dd) {
        onChange(`${yy}-${String(mm).padStart(2,"0")}-${String(dd).padStart(2,"0")}`);
      } else {
        onChange("");
      }
    };
    const t = (k) => (window.I18N ? window.I18N.t(k) : k);
    const now = new Date();
    const curYear = now.getFullYear();
    const years = [];
    for (let i = curYear; i >= curYear - 110; i--) years.push(i);
    const daysInMonth = (yy, mm) => {
      if (!yy || !mm) return 31;
      return new Date(parseInt(yy), parseInt(mm), 0).getDate();
    };
    const maxDay = daysInMonth(y, m);
    return (
      <div className="div-row">
        <label>
          {label}
          <span className="opt">{optional}</span>
        </label>
        <div className="div-date-trio">
          <select className="div-select" value={y} onChange={e => { setY(e.target.value); update(e.target.value, m, d); }}>
            <option value="">{t('year_ph')}</option>
            {years.map(yr => <option key={yr} value={yr}>{yr}</option>)}
          </select>
          <select className="div-select" value={m} onChange={e => { setM(e.target.value); const dd = parseInt(d) > daysInMonth(y, e.target.value) ? "" : d; setD(dd); update(y, e.target.value, dd); }}>
            <option value="">{t('month_ph')}</option>
            {Array.from({length: 12}, (_, i) => i + 1).map(mm => <option key={mm} value={mm}>{String(mm).padStart(2,"0")}</option>)}
          </select>
          <select className="div-select" value={d} onChange={e => { setD(e.target.value); update(y, m, e.target.value); }}>
            <option value="">{t('day_ph')}</option>
            {Array.from({length: maxDay}, (_, i) => i + 1).map(dd => <option key={dd} value={dd}>{String(dd).padStart(2,"0")}</option>)}
          </select>
        </div>
      </div>
    );
  }

  /* ========== Component ========== */
  function Divination() {
    useLangBump();
    const [open, setOpen] = useState(false);
    const [showCfg, setShowCfg] = useState(false);
    const [cfg, setCfgState] = useState(getCfg());
    const [form, setForm] = useState({ country: "CN", name: "", gender: "M", birthday: "" });
    const [phase, setPhase] = useState("form"); // form | login | divining | payment | report
    const [report, setReport] = useState("");
    const [error, setError] = useState("");
    const [provider, setProvider] = useState("auto");
    const [progress, setProgress] = useState(0); // 0..1
    const [posterUrl, setPosterUrl] = useState(null);
    // Session
    const [session,     setSessionState] = useState(() => getSession()); // { token, email, usesRemaining }
    const updateSession = (s) => { saveSession(s); setSessionState(s); };
    // Payment
    const [payConfig,   setPayConfig]   = useState({ clientId: '', mode: 'sandbox', price: '9.90' });
    const [payError,    setPayError]    = useState('');
    const [payLoading,  setPayLoading]  = useState(false);
    const paypalRendered = useRef(false);
    // Login form
    const [loginStep,    setLoginStep]   = useState('email'); // 'email' | 'otp'
    const [loginEmail,   setLoginEmail]  = useState('');
    const [loginOtp,     setLoginOtp]    = useState('');
    const [loginError,   setLoginError]  = useState('');
    const [loginLoading, setLoginLoading]= useState(false);

    // Tick countdown when divining
    useEffect(() => {
      if (phase !== "divining") return;
      const start = performance.now();
      let raf;
      const tick = () => {
        const t = (performance.now() - start) / SPIN_MS;
        setProgress(Math.min(1, t));
        if (t < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, [phase]);

    // 首次加载：验证已存储的会话，刷新剩余次数
    useEffect(() => {
      const s = getSession();
      if (!s?.token) return;
      fetch('/api/auth/profile', { headers: { 'Authorization': `Bearer ${s.token}` } })
        .then(r => r.json())
        .then(d => {
          if (d.success) {
            updateSession({ ...s, usesRemaining: d.usesRemaining });
          } else {
            clearSession();
            setSessionState(null);
          }
        })
        .catch(() => {});
    }, []);

    // 进入 payment 页面：加载 PayPal SDK 并渲染按钮
    useEffect(() => {
      if (phase !== 'payment') return;
      setPayError('');
      setPayLoading(false);
      paypalRendered.current = false;
      let cancelled = false;

      const doRender = () => {
        if (cancelled || paypalRendered.current) return;
        const container = document.getElementById('paypal-button-container');
        if (!container) { setTimeout(doRender, 80); return; }
        if (!window.paypal) return;
        container.innerHTML = '';
        paypalRendered.current = true;
        window.paypal.Buttons({
          style: { layout: 'vertical', color: 'gold', shape: 'pill', label: 'pay' },
          createOrder: async () => {
            const res  = await fetch('/api/paypal/create-order', { method: 'POST' });
            const data = await res.json();
            if (!data.success) throw new Error(data.error || '创建订单失败');
            return data.orderID;
          },
          onApprove: async (approveData) => {
            if (cancelled) return;
            setPayLoading(true);
            try {
              const s = getSession();
              const res    = await fetch('/api/paypal/capture-order', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ orderID: approveData.orderID, sessionToken: s?.token }),
              });
              const result = await res.json();
              if (!result.success) throw new Error(result.error || '订单确认失败');
              if (!cancelled) {
                updateSession({ ...s, usesRemaining: result.usesRemaining });
                setPayLoading(false);
                setPhase('form');
                setOpen(true);
              }
            } catch (e) {
              if (!cancelled) { setPayLoading(false); setPayError('支付确认失败：' + e.message); }
            }
          },
          onError: () => { if (!cancelled) setPayError('支付失败，请重试'); },
        }).render('#paypal-button-container');
      };

      fetch('/api/paypal/config')
        .then(r => r.json())
        .then(cfg => {
          if (cancelled) return;
          setPayConfig(cfg);
          if (!cfg.clientId) { setPayError('支付暂未配置，请联系管理员'); return; }
          if (window.paypal) { doRender(); return; }
          if (document.getElementById('paypal-sdk')) { doRender(); return; }
          const s    = document.createElement('script');
          s.id       = 'paypal-sdk';
          s.src      = `https://www.paypal.com/sdk/js?client-id=${cfg.clientId}&currency=USD`;
          s.onload   = () => { if (!cancelled) doRender(); };
          s.onerror  = () => { if (!cancelled) setPayError('PayPal SDK 加载失败，请检查网络'); };
          document.head.appendChild(s);
        })
        .catch(() => { if (!cancelled) setPayError('加载支付配置失败'); });

      return () => { cancelled = true; };
    }, [phase]);

    // ── 邮箱 OTP 登录 ──
    const safeJson = async (res) => {
      const text = await res.text();
      if (!text) throw new Error(`服务器无响应 (${res.status})，请检查部署状态`);
      try { return JSON.parse(text); }
      catch { throw new Error(`服务器返回非 JSON (${res.status}): ${text.slice(0, 100)}`); }
    };

    const sendOtp = async () => {
      setLoginLoading(true); setLoginError('');
      try {
        const res  = await fetch('/api/auth/send-otp', {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email: loginEmail.trim() }),
        });
        const data = await safeJson(res);
        if (!data.success) throw new Error(data.error || '发送失败');
        setLoginStep('otp');
      } catch (e) { setLoginError(e.message); }
      finally     { setLoginLoading(false); }
    };

    const verifyOtp = async () => {
      setLoginLoading(true); setLoginError('');
      try {
        const res  = await fetch('/api/auth/verify-otp', {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email: loginEmail.trim(), otp: loginOtp.trim() }),
        });
        const data = await safeJson(res);
        if (!data.success) throw new Error(data.error || '验证失败');
        updateSession({ token: data.token, email: data.email, usesRemaining: data.usesRemaining });
        setPhase('form'); setOpen(true);
        setLoginStep('email'); setLoginEmail(''); setLoginOtp('');
      } catch (e) { setLoginError(e.message); }
      finally     { setLoginLoading(false); }
    };

    const handleSubmit = async () => {
      // ── 暂时开放免费使用，跳过登录和次数检查 ──
      setError(""); setReport(""); setPosterUrl(null);
      setOpen(false);
      setPhase("divining");
      window.dispatchEvent(new CustomEvent("compass-burst", { detail: { duration: SPIN_MS } }));

      const prompt    = buildPrompt(form);
      const aiPromise = callAI(prompt, provider, cfg, null);

      await new Promise(r => setTimeout(r, SPIN_MS));
      setPhase("report"); setOpen(true);
      window.dispatchEvent(new CustomEvent("compass-stop"));

      try {
        const { text } = await aiPromise;
        setReport(text);
      } catch (e) {
        setError(e.message || String(e));
      }
    };

    const saveCfg = () => { setCfg(cfg); setCfgState({ ...cfg }); setShowCfg(false); };

    const sections = parseReport(report);

    const onSavePoster = async () => {
      const c = generatePoster(form, sections);
      setPosterUrl(c.toDataURL("image/png"));
      await savePoster(c, form.name);
    };

    const reset = () => {
      setPhase("form"); setReport(""); setError(""); setPosterUrl(null);
      setPayError(''); setLoginError('');
    };

    return (
      <>
        {phase === "form" && (
          <button className="div-fab" onClick={() => setOpen(true)}>
            <span className="div-fab-label">{t('fab')}</span>
          </button>
        )}

        {/* Floating divining indicator */}
        {phase === "divining" && (
          <div className="div-spin-indicator">
            <div className="div-spin-title">{t('spin_title')}</div>
            <div className="div-spin-bar">
              <div className="div-spin-fill" style={{ width: `${progress * 100}%` }} />
            </div>
            <div className="div-spin-meta">
              {t('spin_sub')}
              <span className="dot">·</span>
              {Math.max(0, Math.ceil(SPIN_MS/1000 * (1 - progress)))}s
            </div>
          </div>
        )}

        {/* ── 登录页 ── */}
        {phase === 'login' && (
          <div className="div-overlay" onClick={(e) => { if (e.target === e.currentTarget) { setPhase('form'); setOpen(true); } }}>
            <div className="div-modal">
              <div className="div-head">
                <div className="div-title">
                  <span className="div-title-zh">登录 · 注册</span>
                  <span className="div-title-en">Email Login</span>
                </div>
                <button className="div-icon-btn" title="关闭" onClick={() => { setPhase('form'); setOpen(true); }}>✕</button>
              </div>
              <div className="div-body single">
                <div className="div-form">
                  {loginStep === 'email' ? (
                    <>
                      <p className="div-login-desc">输入邮箱，我们将发送一次性验证码</p>
                      <div className="div-row">
                        <label>邮箱</label>
                        <input
                          className="div-login-input"
                          type="email"
                          value={loginEmail}
                          onChange={e => setLoginEmail(e.target.value)}
                          placeholder="your@email.com"
                          onKeyDown={e => e.key === 'Enter' && loginEmail.trim() && !loginLoading && sendOtp()}
                        />
                      </div>
                      {loginError && <div className="div-error">⚠ {loginError}</div>}
                      <button
                        className="div-submit"
                        disabled={!loginEmail.trim() || loginLoading}
                        onClick={sendOtp}
                      >
                        {loginLoading ? '发送中…' : '发送验证码'}
                      </button>
                    </>
                  ) : (
                    <>
                      <p className="div-login-desc">验证码已发至 <b>{loginEmail}</b>，5 分钟内有效</p>
                      <div className="div-row">
                        <label>验证码</label>
                        <input
                          className="div-login-input"
                          type="text"
                          inputMode="numeric"
                          value={loginOtp}
                          onChange={e => setLoginOtp(e.target.value.replace(/\D/g,''))}
                          placeholder="6 位数字"
                          maxLength={6}
                          onKeyDown={e => e.key === 'Enter' && loginOtp.trim() && !loginLoading && verifyOtp()}
                        />
                      </div>
                      {loginError && <div className="div-error">⚠ {loginError}</div>}
                      <button
                        className="div-submit"
                        disabled={loginOtp.length < 6 || loginLoading}
                        onClick={verifyOtp}
                      >
                        {loginLoading ? '验证中…' : '确认登录'}
                      </button>
                      <button className="div-back-btn" onClick={() => { setLoginStep('email'); setLoginOtp(''); setLoginError(''); }}>← 重新发送</button>
                    </>
                  )}
                </div>
              </div>
            </div>
          </div>
        )}

        {/* ── 支付墙 ── */}
        {phase === 'payment' && (
          <div className="pay-wall-overlay">
            <div className="pay-wall-card">
              <div className="pay-wall-symbol">☰</div>
              <h2 className="pay-wall-title">解锁 · 玄机罗盘</h2>
              <p className="pay-wall-desc">购买后获得 {PAID_USES} 次推演机会</p>
              <div className="pay-wall-features">
                <div className="pay-wall-feature">✦ 每次购买获得 {PAID_USES} 次使用</div>
                <div className="pay-wall-feature">✦ 完整七象心象报告</div>
                <div className="pay-wall-feature">✦ 账户余额长期有效</div>
              </div>
              <div className="pay-wall-price-row">
                <span className="pay-wall-usd">USD</span>
                <span className="pay-wall-amount">${payConfig.price}</span>
                <span className="pay-wall-once">一次性</span>
              </div>
              {payError && <div className="pay-wall-error">⚠ {payError}</div>}
              {payLoading
                ? <div className="pay-wall-loading">正在确认支付…</div>
                : <div id="paypal-button-container" className="pay-wall-paypal" />
              }
              <button className="pay-wall-back" onClick={() => { setPhase('form'); setOpen(true); }}>← 返回</button>
            </div>
          </div>
        )}

        {open && (
          <div className="div-overlay" onClick={(e) => { if (e.target === e.currentTarget && phase !== "divining") { setOpen(false); if (phase === "report") reset(); } }}>
            <div className={"div-modal " + (phase === "report" ? "report" : "")}>
              <div className="div-head">
                <div className="div-title">
                  <span className="div-title-zh">{phase === "report" ? t('report_title') : t('modal_title')}</span>
                  <span className="div-title-en">{t('modal_subtitle')}</span>
                </div>
                <div className="div-head-actions">
                  {phase === "form" && <button className="div-icon-btn" title={t('cfg_title')} onClick={() => setShowCfg(s => !s)}>⚙</button>}
                  {phase === "report" && <button className="div-icon-btn" title={t('cancel')} onClick={reset}>↻</button>}
                  <button className="div-icon-btn" title={t('report_close')} onClick={() => setOpen(false)}>✕</button>
                </div>                
              </div>

              {showCfg && phase === "form" && (
                <div className="div-cfg">
                  <div className="div-cfg-row">
                    <label>{t('cfg_account')}</label>
                    <input type="text" value={cfg.accountId || ""} onChange={e => setCfgState({ ...cfg, accountId: e.target.value.trim() })} placeholder="33-char hex" />
                  </div>
                  <div className="div-cfg-row">
                    <label>{t('cfg_token')}</label>
                    <input type="password" value={cfg.token || ""} onChange={e => setCfgState({ ...cfg, token: e.target.value.trim() })} placeholder="Workers AI Token" />
                  </div>
                  <div className="div-cfg-row">
                    <label>{t('cfg_model')}</label>
                    <input type="text" value={cfg.model || CF_MODEL} onChange={e => setCfgState({ ...cfg, model: e.target.value })} placeholder={CF_MODEL} />
                  </div>
                  <div className="div-cfg-row">
                    <label>{t('cfg_mode')}</label>
                    <select value={provider} onChange={e => setProvider(e.target.value)}>
                      <option value="auto">{t('mode_auto')}</option>
                      <option value="cloudflare">{t('mode_cf')}</option>
                      <option value="claude">{t('mode_fb')}</option>
                    </select>
                  </div>
                  <div className="div-cfg-actions">
                    <button onClick={saveCfg}>{t('cfg_save')}</button>
                    <span className="div-cfg-hint">{t('cfg_note')}</span>
                  </div>
                </div>
              )}

              {phase === "form" && (
                <div className="div-body single">
                  <div className="div-form">
                    <div className="div-row">
                      <label>{t('field_country')}</label>
                      <select className="div-select" value={form.country} onChange={e => setForm({ ...form, country: e.target.value })}>
                        {COUNTRIES.map(c => (
                          <option key={c.code} value={c.code}>{c[getLang()] || c.en}</option>
                        ))}
                      </select>
                    </div>
                    <div className="div-row">
                      <label>{t('field_name')} <span className="req">*</span></label>
                      <input type="text" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} placeholder="" />
                    </div>
                    <div className="div-row">
                      <label>{t('field_gender')}</label>
                      <div className="div-genders">
                        {[['M', t('gender_m')],['F', t('gender_f')],['O', t('gender_o')]].map(([k,g]) => (
                          <button key={k} className={"div-gender" + (form.gender === k ? " active" : "")} onClick={() => setForm({ ...form, gender: k })}>{g}</button>
                        ))}
                      </div>
                    </div>
                    <DateField
                      label={t('field_birth')}
                      value={form.birthday}
                      onChange={(v) => setForm({ ...form, birthday: v })}
                      optional={t('optional')}
                    />
                    {form.birthday && calcInfo(form.birthday) && (
                      <div className="div-meta">
                        <span>{calcInfo(form.birthday).ganzhi} · {t('ganzhi_hint')}</span>
                        <span>·</span>
                        <span>{calcInfo(form.birthday).zodiac}</span>
                      </div>
                    )}
                    {error && (
                      <div className="div-error">⚠ {error}</div>
                    )}
                    {/* 登录入口暂时屏蔽 */}
                    <button className="div-submit" disabled={!form.name.trim()} onClick={handleSubmit}>
                      {t('submit')}
                    </button>
                    <div className="div-tip">{t('spin_sub')} · ~10s</div>
                  </div>
                </div>
              )}

              {phase === "report" && (
                <div className="div-report-wrap">
                  <div className="div-report-head">
                    <div className="div-stamp">易</div>
                    <div>
                      <div className="div-report-title">{t('report_for')} {form.name}</div>
                      <div className="div-report-sub">
                        {(() => { const c = COUNTRIES.find(x => x.code === form.country); return c ? (c[getLang()] || c.en) : form.country; })()}
                        {form.birthday && ` · ${form.birthday}`}
                        {form.birthday && calcInfo(form.birthday) && ` · ${calcInfo(form.birthday).ganzhi} · ${calcInfo(form.birthday).zodiac}`}
                      </div>
                    </div>
                  </div>

                  {error && <div className="div-error">⚠ {error}</div>}

                  {!error && sections.length === 0 && (
                    <div className="div-ai-wait">
                      <div className="div-ai-wait-icon">☯</div>
                      <div className="div-ai-wait-text">{t('spin_title')}</div>
                    </div>
                  )}

                  {sections.length > 0 && (
                    <div className="div-sections">
                      {sections.map((s, i) => (
                        <div key={i} className="div-section">
                          <div className="div-section-head">
                            <span className="div-section-title">{s.title}</span>
                          </div>
                          <div className="div-section-body">{s.body}</div>
                        </div>
                      ))}
                    </div>
                  )}

                  <div className="div-actions">
                    <button className="div-action primary" onClick={onSavePoster}>
                      <span>📿</span> {t('poster_save')}
                    </button>
                    <button className="div-action" onClick={reset}>↻</button>
                  </div>
                  {posterUrl && (
                    <div className="div-poster-preview">
                      <img src={posterUrl} alt="poster preview" />
                      <div className="div-poster-tip">{t('poster_tip')}</div>
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        )}
      </>
    );
  }

  window.Divination = Divination;
})();
