/* 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 = 15000;

  // 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, contemplative English. No markdown.',
      es: 'Escribe en español claro y contemplativo. Sin markdown.',
      ja: '簡潔で詩的な日本語で書いてください。マークダウンは使わない。',
    }[lang] || 'Write in clear, contemplative English.';

    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 请求失败");
    return data.result.response || data.result.output || JSON.stringify(data.result);
  }

  /** 优先：通过 Pages Function 代理（Token 留在服务端） */
  async function callProxy(prompt) {
    const resp = await fetch("/api/ai", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(CF_MESSAGES_WRAPPER(prompt)),
    });
    const data = await resp.json();
    return extractResult(data);
  }

  /** 降级：用户在 ⚙ 中手动填写的凭证直连 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 extractResult(data);
  }

  /** 最后降级：Claude Code 内置预览环境 */
  async function callClaude(prompt) {
    if (!window.claude || typeof window.claude.complete !== "function") {
      throw new Error("未检测到 AI 接口。请在 Cloudflare Pages → Settings → Environment variables 中配置 CF_AI_TOKEN，或在配置（⚙）中手动填入凭证。");
    }
    return await window.claude.complete(prompt);
  }

  /** 统一入口：代理 → 客户端配置 → window.claude */
  async function callAI(prompt, provider, cfg) {
    // 1. 服务端代理（已部署时优先使用）
    if (provider !== "claude") {
      try {
        return await callProxy(prompt);
      } catch (e) {
        // 若是 503（未配置凭证）或网络错误则继续降级；其他错误直接抛出
        if (!e.message.includes("未配置") && !String(e).includes("503") && !String(e).includes("fetch")) throw e;
      }
    }
    // 2. 用户手动配置的 Cloudflare 凭证
    if ((provider === "cloudflare" || provider === "auto") && cfg.accountId && cfg.token) {
      return await callCloudflare(prompt, cfg);
    }
    // 3. Claude Code 预览环境
    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;
    // Estimate height
    let H = 1100;
    sections.forEach(s => { H += 80 + Math.ceil(s.body.length / 18) * 36; });
    H = Math.max(H, 1334);

    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);

    // Subtle 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
    ctx.fillStyle = "#fff";
    for (let i = 0; i < 80; i++) {
      const x = Math.random() * W, y = Math.random() * H;
      const r = Math.random() * 1.4 + 0.3;
      ctx.globalAlpha = 0.3 + Math.random() * 0.6;
      ctx.beginPath(); ctx.arc(x, y, r, 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.strokeRect(28, 28, W - 56, H - 56);

    // Title
    ctx.fillStyle = "#ffe9a8";
    ctx.font = "700 50px 'Ma Shan Zheng', 'STKaiti', serif";
    ctx.textAlign = "center";
    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);

    // Mini bagua circle
    const cx = W/2, cy = 260;
    const 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(cx, cy, rr, 0, Math.PI*2); ctx.stroke();
    });
    // Trigrams around
    const trigs = ["☰","☱","☲","☳","☴","☵","☶","☷"];
    ctx.fillStyle = "#ffce5c";
    ctx.font = "20px serif";
    trigs.forEach((t, i) => {
      const a = (i / 8) * Math.PI * 2 - Math.PI/2;
      ctx.fillText(t, cx + (R - 12) * Math.cos(a), cy + (R - 12) * Math.sin(a) + 7);
    });
    // Yin-yang center
    ctx.fillStyle = "#ffe9a8";
    ctx.beginPath(); ctx.arc(cx, cy, 22, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = "#1a0810";
    ctx.beginPath();
    ctx.arc(cx, cy, 22, -Math.PI/2, Math.PI/2);
    ctx.arc(cx, cy + 11, 11, Math.PI/2, -Math.PI/2, true);
    ctx.arc(cx, cy - 11, 11, Math.PI/2, -Math.PI/2);
    ctx.closePath(); ctx.fill();
    ctx.fillStyle = "#1a0810";
    ctx.beginPath(); ctx.arc(cx, cy - 11, 3, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = "#ffe9a8";
    ctx.beginPath(); ctx.arc(cx, cy + 11, 3, 0, Math.PI*2); ctx.fill();

    // User info card
    let y = 410;
    ctx.fillStyle = "rgba(255,206,92,0.08)";
    ctx.fillRect(50, y - 30, W - 100, 110);
    ctx.strokeStyle = "rgba(255,206,92,0.3)";
    ctx.strokeRect(50, y - 30, W - 100, 110);

    ctx.textAlign = "left";
    ctx.fillStyle = "#ffe9a8";
    ctx.font = "600 26px 'Ma Shan Zheng', 'STKaiti', serif";
    ctx.fillText(form.name || "无名氏", 70, y);
    const info = calcInfo(form.birthday);
    ctx.font = "14px 'Noto Serif SC', serif";
    ctx.fillStyle = "rgba(255,233,168,0.7)";
    const infoLine = `${form.country || "中国"}　·　${form.gender}　·　${form.birthday || ""}`;
    ctx.fillText(infoLine, 70, y + 28);
    if (info) {
      ctx.fillStyle = "#ffce5c";
      ctx.font = "16px 'Ma Shan Zheng', serif";
      ctx.fillText(`${info.ganzhi}年　·　属 ${info.zodiac}`, 70, y + 56);
    }

    y += 130;

    // Sections
    sections.forEach((s, i) => {
      // Title bar
      ctx.fillStyle = "rgba(255,206,92,0.12)";
      ctx.fillRect(50, y, 5, 32);
      ctx.fillStyle = "#ffce5c";
      ctx.font = "600 22px 'Ma Shan Zheng', 'STKaiti', serif";
      ctx.textAlign = "left";
      ctx.shadowColor = "rgba(255,206,92,0.4)";
      ctx.shadowBlur = 8;
      ctx.fillText(`${["一","二","三","四","五","六","七"][i] || (i+1)}、${s.title}`, 65, y + 24);
      ctx.shadowBlur = 0;
      y += 50;
      // Body
      ctx.fillStyle = "rgba(255,233,168,0.92)";
      ctx.font = "16px 'Noto Serif SC', serif";
      const text = s.body.replace(/\s+/g, "");
      y = wrapText(ctx, text, 70, y + 4, W - 140, 30);
      y += 18;
    });

    y += 10;
    // Stamp
    ctx.fillStyle = "#a52a14";
    ctx.fillRect(W/2 - 40, y, 80, 80);
    ctx.fillStyle = "#fff5d6";
    ctx.font = "600 38px 'Ma Shan Zheng', serif";
    ctx.textAlign = "center";
    ctx.fillText("易", W/2, y + 56);

    y += 110;
    // Footer
    ctx.fillStyle = "rgba(255,233,168,0.5)";
    ctx.font = "13px 'Noto Serif SC', serif";
    ctx.textAlign = "center";
    ctx.fillText("天 　 地 　 玄 　 黄 　 ·  　 宇 　 宙 　 洪 　 荒", W/2, y);
    ctx.fillText(new Date().toLocaleString("zh-CN"), W/2, y + 24);

    return c;
  }

  async function savePoster(canvas, name) {
    const blob = await new Promise(res => canvas.toBlob(res, "image/png"));
    const filename = `${name || "reflection"}_iching.png`;
    if (navigator.share && navigator.canShare && navigator.canShare({ files: [new File([blob], filename, { type: "image/png" })] })) {
      try {
        await navigator.share({
          title: t('report_title'),
          files: [new File([blob], filename, { type: "image/png" })],
        });
        return;
      } catch (e) { /* fall through to download */ }
    }
    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 | divining | 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);

    // 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]);

    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);
      const timerPromise = new Promise(r => setTimeout(r, SPIN_MS));

      try {
        const [text] = await Promise.all([aiPromise, timerPromise]);
        setReport(text);
      } catch (e) {
        setError(e.message || String(e));
      } finally {
        setPhase("report");
        setOpen(true);
      }
    };

    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);
    };

    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>
        )}

        {open && (
          <div className="div-overlay" onClick={(e) => { if (e.target === e.currentTarget && phase !== "divining") setOpen(false); }}>
            <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>

              {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')} · ~15s</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>}

                  {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-num">{["一","二","三","四","五","六","七"][i] || (i+1)}</span>
                            <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;
})();
