/* ============================================================
 * app.jsx — Main app shell, views, AI panel, export, canvas
 * ============================================================ */

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ---- Default strip config ----
const DEFAULT_STRIP = {
  text: "未来可期",
  vertical: true,
  fontId: "xing",
  fontScale: 1,
  inkOverride: null,
  paperId: "cream",
  textureId: "cloud",
  shape: "rect-v",
  showSeal: true,
  sealText: "福",
  sealPosition: "bottom",
  sealRound: false,
  showBorder: false,
  borderStyle: "single",
  showGoldFleck: false,
  shadow: "soft",
  paperWear: false,
  rotate: 0,
  mount: "felt",
};

// Curated templates
const TEMPLATES = [
  { name: "丹朱新岁",  text: "未来可期", paperId: "dan", textureId: "wave", fontId: "kai", shape: "rect-v", sealText: "福", inkOverride: "#1a1612" },
  { name: "宣纸寒梅",  text: "诸事顺遂", paperId: "cream", textureId: "blossom", fontId: "xing", shape: "rect-v", sealText: "吉" },
  { name: "藤黄洒金",  text: "马上有钱", paperId: "saffron", textureId: "gold", fontId: "kai", shape: "rect-v", sealText: "财", showGoldFleck: true },
  { name: "胭脂祥云",  text: "诸事大吉", paperId: "plum", textureId: "cloud", fontId: "xing", shape: "rect-v", sealText: "喜" },
  { name: "青瓷顺遂",  text: "顺遂安康", paperId: "celadon", textureId: "wave", fontId: "kai", shape: "rect-v", sealText: "安" },
  { name: "金粉如意",  text: "如意", paperId: "gold", textureId: "kikko", fontId: "li", shape: "square", sealText: "心", sealPosition: "br" },
  { name: "墨色金光",  text: "暴富", paperId: "ink", textureId: "gold", fontId: "zhuan", shape: "rect-v", sealText: "财", showGoldFleck: true },
  { name: "天青福临",  text: "福", paperId: "twilight", textureId: "fibers", fontId: "kai", shape: "diamond", sealText: "" , showSeal: false, fontScale: 1.4 },
  { name: "正红进财",  text: "招财进宝", paperId: "vermilion", textureId: "gold", fontId: "kai", shape: "rect-v", sealText: "财", inkOverride: "#d4b56a", showGoldFleck: true },
  { name: "石绿春风",  text: "春风得意", paperId: "jade", textureId: "bamboo", fontId: "xing", shape: "rect-v", sealText: "乐" },
  { name: "枯叶常安",  text: "常安", paperId: "sand", textureId: "fibers", fontId: "li", shape: "square", sealText: "福", sealPosition: "br" },
  { name: "雪白闲心",  text: "见自己", paperId: "snow", textureId: "fibers", fontId: "xing", shape: "rect-v", sealText: "心" },
];

// AI prompt presets
const AI_PRESETS = [
  "新春纳福", "事业顺利", "考试上岸", "心想事成", "暴富发财",
  "平安喜乐", "桃花朵朵", "出行顺利", "求职成功", "祛病康健",
];

// Helper: render strip fitted inside a parent box.
function FittedStrip({ s, seed = 1 }) {
  const shapeDef = SHAPES[s.shape || "rect-v"];
  const isTall = shapeDef.ar < 1;
  const sizerStyle = isTall
    ? { height: "94%", aspectRatio: shapeDef.ar }
    : { width: shapeDef.ar > 2 ? "94%" : "70%", aspectRatio: shapeDef.ar };
  return (
    <div style={{ width: "100%", height: "100%", display: "grid", placeItems: "center" }}>
      <div style={sizerStyle}>
        <PaperStrip
          text={s.text}
          shape={s.shape || "rect-v"}
          paperId={s.paperId || "cream"}
          textureId={s.textureId || "cloud"}
          fontId={s.fontId || "xing"}
          fontScale={s.fontScale || 1}
          textColor={s.inkOverride}
          showSeal={s.showSeal !== false}
          sealText={s.sealText || "福"}
          sealPosition={s.sealPosition || "bottom"}
          sealRound={s.sealRound}
          showBorder={s.showBorder}
          borderStyle={s.borderStyle}
          showGoldFleck={s.showGoldFleck}
          vertical={s.vertical}
          shadow={s.shadow || "soft"}
          paperWear={s.paperWear}
          rotate={s.rotate || 0}
          fillSize={true}
          seed={seed}
        />
      </div>
    </div>
  );
}

// ===========================================================
// Topbar
// ===========================================================
function Topbar({ view, setView, onOpenApiKey, onExport, apiKey }) {
  const brand = window.__BRAND__ || { name: "墨笺", seal: "墨", tag: "MÒ · JIĀN" };
  const tabs = [
    { id: "edit",   name: "编辑" },
    { id: "ai",     name: "AI 生成" },
    { id: "canvas", name: "拼贴" },
    { id: "tpl",    name: "纸条谱" },
  ];
  return (
    <div className="topbar">
      <div className="brand">
        <span className="seal">{brand.seal}</span>
        <span className="name">{brand.name}</span>
        <span className="tag">{brand.tag}</span>
      </div>
      <div className="nav">
        {tabs.map(t => (
          <button
            key={t.id}
            className={view === t.id ? "active" : ""}
            onClick={() => setView(t.id)}
          >{t.name}</button>
        ))}
      </div>
      <div className="spacer"/>
      <button className="iconbtn" onClick={onOpenApiKey} title="设置 AI 接口">
        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
          <circle cx="7" cy="7" r="2.5"/>
          <path d="M7 1 L7 3 M7 11 L7 13 M1 7 L3 7 M11 7 L13 7 M2.5 2.5 L4 4 M10 10 L11.5 11.5 M2.5 11.5 L4 10 M10 4 L11.5 2.5"/>
        </svg>
        {apiKey ? "接口已连" : "接口"}
      </button>
      <button className="iconbtn primary" onClick={onExport}>
        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
          <path d="M7 1 L7 9 M3.5 5.5 L7 9 L10.5 5.5 M2 12 L12 12"/>
        </svg>
        导出
      </button>
    </div>
  );
}

// ===========================================================
// Stage — shared preview area
// ===========================================================
function PreviewStage({ strip, stageRef, showHint, tweaks }) {
  // Determine mount-canvas aspect class based on mount
  const mountDef = MOUNTS.find(m => m.id === strip.mount) || MOUNTS[0];
  let aspectClass = "";
  if (mountDef.shape === "tall") aspectClass = "tall";
  else if (mountDef.shape === "wide") aspectClass = "wide";
  else if (mountDef.shape === "square") aspectClass = "square";

  // Strip sizing — by shape aspect ratio, fit inside canvas
  const shapeDef = SHAPES[strip.shape];
  // For tall (ar<1), size by height %. For wide (ar>1), size by width %.
  const isTall = shapeDef.ar < 1;
  const sizerStyle = isTall
    ? { height: "78%", aspectRatio: shapeDef.ar }
    : { width: shapeDef.ar > 2 ? "78%" : "55%", aspectRatio: shapeDef.ar };

  return (
    <div className="stage">
      <div className="stage-head">
        <span className="title">预览 · {SHAPES[strip.shape].name}</span>
        <span className="chip">{PAPER_COLORS.find(p => p.id === strip.paperId)?.name}</span>
        <span className="chip">{FONTS.find(f => f.id === strip.fontId)?.name}</span>
        <span className="chip">{PAPER_TEXTURES.find(t => t.id === strip.textureId)?.name}</span>
        <span className="meta">{MOUNTS.find(m => m.id === strip.mount)?.name}</span>
      </div>
      <div className="stage-body">
        <div className={`mount-canvas ${aspectClass}`} ref={stageRef} data-export-target>
          <MountScene kind={strip.mount}>
            <div style={sizerStyle}>
              <PaperStrip
                text={strip.text}
                shape={strip.shape}
                paperId={strip.paperId}
                textureId={strip.textureId}
                fontId={strip.fontId}
                fontScale={strip.fontScale}
                textColor={strip.inkOverride}
                showSeal={strip.showSeal}
                sealText={strip.sealText}
                sealPosition={strip.sealPosition}
                sealRound={strip.sealRound}
                showBorder={strip.showBorder}
                borderStyle={strip.borderStyle}
                showGoldFleck={strip.showGoldFleck}
                vertical={strip.vertical}
                shadow={strip.shadow}
                paperWear={strip.paperWear}
                rotate={strip.rotate}
                fillSize={true}
                seed={1}
              />
            </div>
          </MountScene>
        </div>
      </div>
      <div className="stage-foot">
        <span>{showHint || "笺纸轻提，墨字落定 ·  调整左侧任意一项，实时同步"}</span>
        <span className="right">
          <span className="muted">{strip.text.length} 字</span>
        </span>
      </div>
    </div>
  );
}

// ===========================================================
// Editor view — left controls + center stage + right actions
// ===========================================================
function EditView({ strip, set, onAddToCanvas, onSaveTemplate, onExport, onCopy }) {
  const stageRef = useRef(null);

  return (
    <div className="workspace">
      <div className="left-rail">
        <TextPanel state={strip} set={set}/>
        <FontPanel state={strip} set={set}/>
        <PaperPanel state={strip} set={set}/>
        <ShapePanel state={strip} set={set}/>
        <DecoPanel state={strip} set={set}/>
        <LightPanel state={strip} set={set}/>
        <MountPanel state={strip} set={set}/>
      </div>
      <PreviewStage strip={strip} stageRef={stageRef}/>
      <div className="right-rail">
        <div className="deco-line">操 作</div>
        <div className="action-list">
          <button className="action primary" onClick={() => onExport(stageRef.current)}>
            <span className="icon">
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
                <path d="M7 1 L7 9 M3.5 5.5 L7 9 L10.5 5.5 M2 12 L12 12"/>
              </svg>
            </span>
            <span className="body">
              <span className="label">下载 PNG</span>
              <span className="desc">含场景的位图</span>
            </span>
          </button>
          <button className="action" onClick={() => onExport(stageRef.current, "transparent")}>
            <span className="icon">
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
                <rect x="2" y="2" width="10" height="10" rx="1"/>
                <path d="M2 2 L12 12 M2 12 L12 2" opacity="0.3"/>
              </svg>
            </span>
            <span className="body">
              <span className="label">透明背景</span>
              <span className="desc">仅纸条本体</span>
            </span>
          </button>
          <button className="action" onClick={() => onCopy(stageRef.current)}>
            <span className="icon">
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
                <rect x="3" y="3" width="8" height="9" rx="1"/>
                <path d="M5 1 L11 1 L11 10"/>
              </svg>
            </span>
            <span className="body">
              <span className="label">复制图像</span>
              <span className="desc">至剪贴板</span>
            </span>
          </button>
          <button className="action" onClick={() => onAddToCanvas(strip)}>
            <span className="icon">
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
                <path d="M7 2 L7 12 M2 7 L12 7"/>
              </svg>
            </span>
            <span className="body">
              <span className="label">加入拼贴</span>
              <span className="desc">放入画布</span>
            </span>
          </button>
          <button className="action" onClick={() => onSaveTemplate(strip)}>
            <span className="icon">
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.2">
                <path d="M3 2 L11 2 L11 12 L7 10 L3 12 Z"/>
              </svg>
            </span>
            <span className="body">
              <span className="label">收藏配方</span>
              <span className="desc">存为模板</span>
            </span>
          </button>
        </div>

        <div className="deco-line">速 选</div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
          {TEMPLATES.slice(0, 6).map((t, i) => (
            <button
              key={i}
              className="opt"
              onClick={() => set({ ...DEFAULT_STRIP, ...t })}
              style={{ height: 56, fontFamily: 'var(--serif)', padding: "6px 4px", textAlign: "center" }}
            >
              <div style={{ fontSize: 12, color: "var(--ink)" }}>{t.text}</div>
              <div className="sub">{t.name}</div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ===========================================================
// AI view
// ===========================================================
function AiView({ strip, set, apiKey, onOpenApiKey, library, addToLibrary }) {
  const [mood, setMood] = useState("");
  const [styleHint, setStyleHint] = useState("禅意东方，简素干净");
  const [count, setCount] = useState(3);
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState("");

  const generate = useCallback(async () => {
    setErr(""); setLoading(true);
    try {
      // Use built-in claude.complete if no API key, otherwise call user-supplied API.
      const prompt = `你是中国传统书法纸条设计师。请根据用户的心情或主题，生成 ${count} 张吉祥纸条建议。

用户主题: ${mood || "随意"}
风格偏好: ${styleHint}

每张纸条返回以下 JSON 字段（严格的 JSON 数组，无任何额外文本）：
- text:  4字以内的吉祥语，简体
- paperId:  从这些里选: cream snow rice kraft dan vermilion ochre saffron celadon jade indigo twilight plum lilac sand moss ink gold
- textureId: 从这些里选: plain cloud gold wave kikko bamboo blossom fibers
- fontId: 从这些里选: kai song xing cao caoZhi li zhuan shou maocao
- shape: 从这些里选: rect-v rect-h square diamond circle coin gourd flower pouch tall
- sealText: 1 个汉字
- name: 一个 4 字以内的纸条诗意名称

例如:
[
  {"text":"未来可期","paperId":"dan","textureId":"wave","fontId":"kai","shape":"rect-v","sealText":"福","name":"丹朱新岁"},
  ...
]`;

      let text;
      if (apiKey && apiKey.provider) {
        // user-supplied provider — mock the call (we cannot reach external APIs reliably in sandbox)
        // We'll still try; fallback to local if failure.
        try {
          text = await callExternalApi(apiKey, prompt);
        } catch (e) {
          console.warn("External API failed, falling back to built-in:", e);
          text = await window.claude.complete(prompt);
        }
      } else {
        text = await window.claude.complete(prompt);
      }

      // Parse JSON
      const match = text.match(/\[[\s\S]*\]/);
      if (!match) throw new Error("AI 未返回有效格式");
      const data = JSON.parse(match[0]);
      setResults(data);
    } catch (e) {
      console.error(e);
      setErr(String(e.message || e));
      // Fallback: local random pick from templates
      const shuffled = [...TEMPLATES].sort(() => Math.random() - 0.5).slice(0, count);
      setResults(shuffled);
    } finally {
      setLoading(false);
    }
  }, [mood, styleHint, count, apiKey]);

  return (
    <div className="workspace" style={{ gridTemplateColumns: "360px 1fr" }}>
      <div className="left-rail">
        <div className="ai-panel">
          <div className="ai-head">
            <span>AI 笺师</span>
            <span className="badge">BETA</span>
          </div>
          <div className="muted">说出你的心境，由 AI 推荐文字、配色与字体组合</div>
          <Field label="心情或主题">
            <input
              className="input"
              value={mood}
              onChange={(e) => setMood(e.target.value)}
              placeholder="如：希望今年事业蒸蒸日上"
            />
          </Field>
          <Field label="速选">
            <div className="preset-row">
              {AI_PRESETS.map((p, i) => (
                <button key={i} className="pill" onClick={() => setMood(p)}>{p}</button>
              ))}
            </div>
          </Field>
          <Field label="风格偏好">
            <input
              className="input"
              value={styleHint}
              onChange={(e) => setStyleHint(e.target.value)}
            />
          </Field>
          <Field label="生成数量" value={count}>
            <input
              type="range" min="1" max="9" step="1"
              value={count}
              onChange={(e) => setCount(parseInt(e.target.value))}
              className="slider"
            />
          </Field>
          <button
            className="action primary"
            onClick={generate}
            disabled={loading}
            style={{ marginTop: 4 }}
          >
            <span className="icon">
              {loading ? (
                <svg width="14" height="14" viewBox="0 0 14 14"><circle cx="7" cy="7" r="5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeDasharray="20 10"><animateTransform attributeName="transform" type="rotate" from="0 7 7" to="360 7 7" dur="1s" repeatCount="indefinite"/></circle></svg>
              ) : (
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
                  <path d="M7 1 L8.5 5.5 L13 7 L8.5 8.5 L7 13 L5.5 8.5 L1 7 L5.5 5.5 Z"/>
                </svg>
              )}
            </span>
            <span className="body">
              <span className="label">{loading ? "凝神运笔中…" : "生成纸条"}</span>
              <span className="desc">{apiKey?.provider ? `接口 · ${apiKey.provider}` : "内置 · 即时返回"}</span>
            </span>
          </button>
          {err && <div className="muted" style={{ color: "var(--dan)" }}>提示：{err}</div>}
          {!apiKey?.provider && (
            <div className="muted" style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 10 }}>
              当前使用内置生成。若需对接你自己的 OpenAI / Anthropic key，请点击右上角"接口"。
            </div>
          )}
        </div>
      </div>
      <div className="stage">
        <div className="stage-head">
          <span className="title">AI · 生成结果</span>
          <span className="meta">{results.length} 条建议</span>
        </div>
        <div className="stage-body" style={{ display: "block", overflow: "auto" }}>
          {results.length === 0 && !loading && (
            <div style={{ display: "grid", placeItems: "center", height: "100%", textAlign: "center", color: "var(--ink-faint)", padding: 40 }}>
              <div>
                <div style={{ fontSize: 80, fontFamily: 'var(--serif)', color: "var(--ink-mute)", opacity: 0.4 }}>笺</div>
                <div style={{ letterSpacing: "0.32em", marginTop: 12 }}>说出心境，落墨成笺</div>
              </div>
            </div>
          )}
          {loading && (
            <div style={{ display: "grid", placeItems: "center", height: "100%", color: "var(--ink-mute)" }}>
              <div style={{ textAlign: "center" }}>
                <div style={{ fontSize: 40, animation: "fade 1.5s ease infinite" }}>墨</div>
                <div className="muted" style={{ marginTop: 10 }}>研墨运笔中…</div>
              </div>
            </div>
          )}
          {!loading && results.length > 0 && (
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 24, padding: 28 }}>
              {results.map((r, i) => (
                <div key={i} className="tpl-card" onClick={() => { set({ ...DEFAULT_STRIP, ...r }); }}>
                  <div className="preview" style={{ background: "transparent", padding: 8 }}>
                    <FittedStrip s={r} seed={i+1}/>
                  </div>
                  <div className="name">{r.name || r.text}</div>
                  <div className="meta">{PAPER_COLORS.find(p => p.id === r.paperId)?.name || ""} · {FONTS.find(f => f.id === r.fontId)?.name || ""}</div>
                </div>
              ))}
            </div>
          )}
        </div>
        <div className="stage-foot">
          <span>点击任一卡片，将设计载入编辑器</span>
        </div>
      </div>
    </div>
  );
}

async function callExternalApi(apiKey, prompt) {
  // Sandbox cannot reach external APIs reliably (CORS / origin). This stub attempts but is expected to be replaced by user's backend.
  const { provider, key } = apiKey;
  if (provider === "openai") {
    const r = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${key}`,
      },
      body: JSON.stringify({
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: prompt }],
        temperature: 0.7,
      }),
    });
    const data = await r.json();
    return data.choices[0].message.content;
  }
  if (provider === "anthropic") {
    const r = await fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": key,
        "anthropic-version": "2023-06-01",
      },
      body: JSON.stringify({
        model: "claude-haiku-4-5",
        max_tokens: 1024,
        messages: [{ role: "user", content: prompt }],
      }),
    });
    const data = await r.json();
    return data.content[0].text;
  }
  throw new Error("未知接口");
}

// ===========================================================
// Canvas / Collage view
// ===========================================================
function CanvasView({ items, setItems, currentStrip, addStripFrom, onExport, apiKey }) {
  const [selected, setSelected] = useState(null);
  const [bg, setBg] = useState("felt");
  const [aiLoading, setAiLoading] = useState(false);
  const boardRef = useRef(null);

  const updateItem = (id, patch) => {
    setItems(items.map(it => it.id === id ? { ...it, ...patch } : it));
  };
  const removeItem = (id) => {
    setItems(items.filter(it => it.id !== id));
    if (selected === id) setSelected(null);
  };

  // Drag logic
  const dragRef = useRef(null);
  const handleMouseDown = (e, item) => {
    e.stopPropagation();
    setSelected(item.id);
    const board = boardRef.current.getBoundingClientRect();
    dragRef.current = {
      id: item.id,
      startX: e.clientX, startY: e.clientY,
      origX: item.x, origY: item.y,
      boardW: board.width, boardH: board.height,
    };
    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);
  };
  const handleMouseMove = (e) => {
    if (!dragRef.current) return;
    const d = dragRef.current;
    const dx = (e.clientX - d.startX) / d.boardW * 100;
    const dy = (e.clientY - d.startY) / d.boardH * 100;
    setItems(prev => prev.map(it => it.id === d.id ? { ...it, x: d.origX + dx, y: d.origY + dy } : it));
  };
  const handleMouseUp = () => {
    dragRef.current = null;
    document.removeEventListener("mousemove", handleMouseMove);
    document.removeEventListener("mouseup", handleMouseUp);
  };

  // Add curated default arrangement
  const addPreset = () => {
    const t = TEMPLATES[Math.floor(Math.random() * TEMPLATES.length)];
    const id = Date.now() + Math.random();
    setItems([...items, {
      id,
      ...DEFAULT_STRIP, ...t,
      x: 20 + Math.random() * 60,
      y: 20 + Math.random() * 50,
      scale: 0.7 + Math.random() * 0.5,
      rotate: -8 + Math.random() * 16,
    }]);
  };

  const addCurrent = () => {
    const id = Date.now() + Math.random();
    setItems([...items, { id, ...currentStrip,
      x: 30 + Math.random() * 40, y: 25 + Math.random() * 40,
      scale: 0.7, rotate: -5 + Math.random() * 10
    }]);
  };

  const clearAll = () => setItems([]);

  const aiCollage = async () => {
    setAiLoading(true);
    const theme = prompt("此次拼贴的主题（例：新春、考试、招财）", "招财进宝");
    if (!theme) { setAiLoading(false); return; }
    try {
      const prompt2 = `生成一组中国传统书法纸条用于拼贴展示，主题：${theme}。
返回严格 JSON 数组，10 条不同的纸条建议，混合不同形状、颜色与字体：
[{"text":"4字以内吉祥语","paperId":"<paper>","textureId":"<texture>","fontId":"<font>","shape":"<shape>","sealText":"1字","name":"4字诗意名称"}]
可选 paperId: cream snow rice kraft dan vermilion ochre saffron celadon jade indigo twilight plum lilac sand moss ink gold
可选 textureId: plain cloud gold wave kikko bamboo blossom fibers
可选 fontId: kai song xing cao caoZhi li zhuan shou maocao
可选 shape: rect-v rect-h square diamond circle coin gourd flower pouch tall
混搭形状与颜色，让拼贴丰富生动。`;
      let text;
      try {
        text = await window.claude.complete(prompt2);
      } catch (e) {
        throw new Error("AI 调用失败");
      }
      const match = text.match(/\[[\s\S]*\]/);
      if (!match) throw new Error("AI 未返回有效格式");
      const data = JSON.parse(match[0]);
      const arr = data.map((d, i) => ({
        id: Date.now() + i,
        ...DEFAULT_STRIP, ...d,
        x: 15 + (i % 4) * 22 + Math.random() * 6,
        y: 22 + Math.floor(i / 4) * 30 + Math.random() * 6,
        scale: 0.55 + Math.random() * 0.3,
        rotate: -10 + Math.random() * 20,
      }));
      setItems(arr);
    } catch (e) {
      toast("AI 生成失败，使用本地配方");
      const arr = [...Array(10)].map((_, i) => {
        const t = TEMPLATES[Math.floor(Math.random() * TEMPLATES.length)];
        return {
          id: Date.now() + i,
          ...DEFAULT_STRIP, ...t,
          x: 15 + (i % 4) * 22 + Math.random() * 6,
          y: 22 + Math.floor(i / 4) * 30 + Math.random() * 6,
          scale: 0.55 + Math.random() * 0.3,
          rotate: -10 + Math.random() * 20,
        };
      });
      setItems(arr);
    } finally {
      setAiLoading(false);
    }
  };

  return (
    <div className="workspace" style={{ gridTemplateColumns: "300px 1fr 260px" }}>
      <div className="left-rail">
        <Panel ord="一" title="画布" en="Canvas">
          <Field label="背景">
            <div className="opt-grid cols-2">
              {MOUNTS.map(m => (
                <button key={m.id} className={`opt ${bg === m.id ? "active" : ""}`} onClick={() => setBg(m.id)}>{m.name}</button>
              ))}
            </div>
          </Field>
        </Panel>

        <Panel ord="二" title="加入" en="Add">
          <div className="action-list">
            <button className="action primary" onClick={addCurrent}>
              <span className="icon">＋</span>
              <span className="body">
                <span className="label">添加当前设计</span>
                <span className="desc">从编辑器载入</span>
              </span>
            </button>
            <button className="action" onClick={addPreset}>
              <span className="icon">籤</span>
              <span className="body">
                <span className="label">随机抽一张</span>
                <span className="desc">从纸条谱挑选</span>
              </span>
            </button>
            <button className="action" onClick={aiCollage} disabled={aiLoading}>
              <span className="icon">{aiLoading ? "…" : "✦"}</span>
              <span className="body">
                <span className="label">{aiLoading ? "AI 运笔中…" : "AI 生成一组"}</span>
                <span className="desc">主题式拼贴</span>
              </span>
            </button>
          </div>
        </Panel>

        {selected && (() => {
          const item = items.find(it => it.id === selected);
          if (!item) return null;
          return (
            <Panel ord="三" title="编辑此条" en="Edit">
              <Field label="文字">
                <input className="input" value={item.text} onChange={(e) => updateItem(item.id, { text: e.target.value })}/>
              </Field>
              <Field label="缩放" value={`${Math.round(item.scale * 100)}%`}>
                <input type="range" min="0.3" max="1.6" step="0.05" value={item.scale}
                       onChange={(e) => updateItem(item.id, { scale: parseFloat(e.target.value) })}
                       className="slider"/>
              </Field>
              <Field label="旋转" value={`${Math.round(item.rotate)}°`}>
                <input type="range" min="-30" max="30" step="1" value={item.rotate}
                       onChange={(e) => updateItem(item.id, { rotate: parseInt(e.target.value) })}
                       className="slider"/>
              </Field>
              <Field label="层级">
                <div className="opt-grid cols-2">
                  <button className="opt" onClick={() => {
                    const idx = items.findIndex(it => it.id === item.id);
                    if (idx > 0) {
                      const arr = [...items]; [arr[idx-1], arr[idx]] = [arr[idx], arr[idx-1]];
                      setItems(arr);
                    }
                  }}>下移</button>
                  <button className="opt" onClick={() => {
                    const idx = items.findIndex(it => it.id === item.id);
                    if (idx < items.length - 1) {
                      const arr = [...items]; [arr[idx+1], arr[idx]] = [arr[idx], arr[idx+1]];
                      setItems(arr);
                    }
                  }}>上移</button>
                </div>
              </Field>
              <button className="action" onClick={() => removeItem(item.id)} style={{ color: "var(--dan)" }}>
                <span className="icon">×</span>
                <span className="body"><span className="label" style={{ color: "var(--dan)" }}>移除此条</span></span>
              </button>
            </Panel>
          );
        })()}
      </div>
      <div className="stage">
        <div className="stage-head">
          <span className="title">拼贴画布</span>
          <span className="meta">{items.length} 张纸条</span>
        </div>
        <div className="stage-body collage-stage" onClick={() => setSelected(null)}>
          <div className="collage-board" ref={boardRef} data-export-target onClick={(e) => e.stopPropagation()}>
            <MountScene kind={bg}>
              {items.map(item => {
                const w = 90 * (item.scale || 1);
                return (
                  <div
                    key={item.id}
                    className={`collage-strip ${selected === item.id ? "selected" : ""}`}
                    style={{
                      left: `${item.x}%`,
                      top: `${item.y}%`,
                      transform: `translate(-50%, -50%) rotate(${item.rotate || 0}deg)`,
                      zIndex: selected === item.id ? 99 : "auto",
                    }}
                    onMouseDown={(e) => handleMouseDown(e, item)}
                    onClick={(e) => { e.stopPropagation(); setSelected(item.id); }}
                  >
                    <PaperStrip
                      text={item.text}
                      shape={item.shape}
                      paperId={item.paperId}
                      textureId={item.textureId}
                      fontId={item.fontId}
                      fontScale={item.fontScale}
                      textColor={item.inkOverride}
                      showSeal={item.showSeal}
                      sealText={item.sealText}
                      sealPosition={item.sealPosition}
                      sealRound={item.sealRound}
                      showBorder={item.showBorder}
                      borderStyle={item.borderStyle}
                      showGoldFleck={item.showGoldFleck}
                      vertical={item.vertical}
                      shadow={item.shadow}
                      width={w}
                      seed={Math.floor(item.id) % 100}
                    />
                  </div>
                );
              })}
            </MountScene>
          </div>
        </div>
        <div className="stage-foot">
          <span>拖动纸条调整位置，点击选中后可在左侧精修</span>
          <span className="right">
            <button className="iconbtn" onClick={clearAll}>清空</button>
          </span>
        </div>
      </div>
      <div className="right-rail">
        <div className="deco-line">导 出</div>
        <div className="action-list">
          <button className="action primary" onClick={() => onExport(document.querySelector("[data-export-target]"))}>
            <span className="icon">↓</span>
            <span className="body">
              <span className="label">下载拼贴图</span>
              <span className="desc">PNG · 含背景</span>
            </span>
          </button>
          <button className="action" onClick={() => {
            // Random arrange — pick 8 templates
            const arr = [...Array(8)].map((_, i) => {
              const t = TEMPLATES[Math.floor(Math.random() * TEMPLATES.length)];
              return {
                id: Date.now() + i,
                ...DEFAULT_STRIP, ...t,
                x: 15 + (i % 4) * 22 + Math.random() * 6,
                y: 22 + Math.floor(i / 4) * 32 + Math.random() * 6,
                scale: 0.55 + Math.random() * 0.3,
                rotate: -10 + Math.random() * 20,
              };
            });
            setItems(arr);
          }}>
            <span className="icon">籤</span>
            <span className="body">
              <span className="label">随机布满</span>
              <span className="desc">8 张拼贴</span>
            </span>
          </button>
        </div>
      </div>
    </div>
  );
}

// ===========================================================
// Templates view
// ===========================================================
function TemplatesView({ set, setView, library }) {
  const all = [...TEMPLATES, ...(library || [])];
  return (
    <div className="stage" style={{ gridColumn: "1 / -1" }}>
      <div className="stage-head">
        <span className="title">纸条谱</span>
        <span className="meta">{all.length} 款配方 · 点击载入编辑器</span>
      </div>
      <div className="stage-body" style={{ display: "block", overflow: "auto" }}>
        <div className="templates-grid">
          {all.map((t, i) => (
            <div key={i} className="tpl-card" onClick={() => {
              set({ ...DEFAULT_STRIP, ...t });
              setView("edit");
            }}>
              <div className="preview" style={{ background: "transparent", padding: 6 }}>
                <FittedStrip s={t} seed={i+1}/>
              </div>
              <div className="name">{t.name || t.text}</div>
              <div className="meta">{(PAPER_COLORS.find(p => p.id === t.paperId) || {}).name} · {(FONTS.find(f => f.id === t.fontId) || {}).name}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ===========================================================
// API Key modal
// ===========================================================
function ApiKeyModal({ onClose, apiKey, setApiKey }) {
  const [provider, setProvider] = useState(apiKey?.provider || "claude");
  const [key, setKey] = useState(apiKey?.key || "");
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h3>AI 接口设置</h3>
        <div className="sub">选择 AI 服务并填写 API key，留空则使用内置</div>
        <Field label="服务">
          <div className="opt-grid">
            {[
              { id: "claude", name: "内置 (Claude)" },
              { id: "openai", name: "OpenAI" },
              { id: "anthropic", name: "Anthropic" },
            ].map(p => (
              <button key={p.id} className={`opt ${provider === p.id ? "active" : ""}`} onClick={() => setProvider(p.id)}>{p.name}</button>
            ))}
          </div>
        </Field>
        {provider !== "claude" && (
          <Field label="API Key">
            <input
              type="password"
              className="input"
              value={key}
              onChange={(e) => setKey(e.target.value)}
              placeholder="sk-..."
              style={{ fontFamily: 'var(--sans)', fontSize: 13, letterSpacing: '0.04em' }}
            />
          </Field>
        )}
        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 }}>
          <button className="iconbtn" onClick={onClose}>取消</button>
          <button className="iconbtn primary" onClick={() => {
            if (provider === "claude") setApiKey(null);
            else setApiKey({ provider, key });
            onClose();
          }}>保存</button>
        </div>
        <div className="muted" style={{ marginTop: 14, lineHeight: 1.7 }}>
          注：浏览器环境下直接调用第三方 API 可能受 CORS 限制。生产环境建议接入自有后端中转。本网站不会上传或保存你的 key（仅存于本地）。
        </div>
      </div>
    </div>
  );
}

// ===========================================================
// Export helpers
// ===========================================================
async function exportNodeToPng(node, transparent = false) {
  if (!node || !window.htmlToImage) return;

  // For transparent mode: hide the mount scene layer (the absolute siblings except the strip)
  let prevBg, prevHidden = [];
  if (transparent) {
    // Hide background siblings within the mount
    const children = node.children;
    for (let i = 0; i < children.length; i++) {
      const c = children[i];
      // The MountScene wraps the strip; for transparent we want only the .strip
      // It's complex — fallback to just rendering with no background.
    }
    prevBg = node.style.background;
    node.style.background = "transparent";
  }

  try {
    const dataUrl = await window.htmlToImage.toPng(node, {
      pixelRatio: 2,
      cacheBust: true,
      backgroundColor: transparent ? null : undefined,
    });
    const link = document.createElement("a");
    link.download = `墨笺-${Date.now()}.png`;
    link.href = dataUrl;
    link.click();
  } finally {
    if (transparent) {
      node.style.background = prevBg || "";
    }
  }
}

async function copyNodeToClipboard(node) {
  if (!node || !window.htmlToImage) return;
  try {
    const blob = await window.htmlToImage.toBlob(node, { pixelRatio: 2, cacheBust: true });
    if (navigator.clipboard && window.ClipboardItem) {
      await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
      toast("已复制到剪贴板");
    } else {
      toast("浏览器不支持剪贴板，已改为下载");
      const link = document.createElement("a");
      link.download = `墨笺-${Date.now()}.png`;
      link.href = URL.createObjectURL(blob);
      link.click();
    }
  } catch (e) {
    console.error(e);
    toast("复制失败：" + e.message);
  }
}

function toast(msg) {
  const t = document.createElement("div");
  t.textContent = msg;
  t.style.cssText = `position:fixed;bottom:32px;left:50%;transform:translateX(-50%);background:#1a1612;color:#f5efe4;padding:10px 20px;border-radius:4px;font-family:var(--serif);letter-spacing:.2em;font-size:13px;z-index:9999;box-shadow:0 6px 20px rgba(0,0,0,.3)`;
  document.body.appendChild(t);
  setTimeout(() => t.remove(), 2400);
}

// ===========================================================
// Tweaks defaults
// ===========================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#c4453a",
  "uiDensity": "正常",
  "stripScale": 1,
  "stageVignette": true,
  "introMode": "编辑"
}/*EDITMODE-END*/;

// ===========================================================
// Root App
// ===========================================================
function App() {
  const [view, setView] = useState("edit");
  const [strip, setStrip] = useState(DEFAULT_STRIP);
  const [showApiModal, setShowApiModal] = useState(false);
  const [apiKey, setApiKey] = useState(() => {
    try { return JSON.parse(localStorage.getItem("mojian.apiKey") || "null"); } catch { return null; }
  });
  const [canvasItems, setCanvasItems] = useState([]);
  const [library, setLibrary] = useState(() => {
    try { return JSON.parse(localStorage.getItem("mojian.library") || "[]"); } catch { return []; }
  });
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  useEffect(() => {
    localStorage.setItem("mojian.apiKey", JSON.stringify(apiKey));
  }, [apiKey]);
  useEffect(() => {
    localStorage.setItem("mojian.library", JSON.stringify(library));
  }, [library]);

  useEffect(() => {
    document.documentElement.style.setProperty("--dan", t.accent);
  }, [t.accent]);

  const set = (patch) => setStrip(prev => ({ ...prev, ...patch }));

  const onExport = useCallback((node, mode) => {
    const target = node || document.querySelector("[data-export-target]");
    exportNodeToPng(target, mode === "transparent");
  }, []);

  const onCopy = useCallback((node) => {
    const target = node || document.querySelector("[data-export-target]");
    copyNodeToClipboard(target);
  }, []);

  const onAddToCanvas = (s) => {
    setCanvasItems(prev => [...prev, {
      id: Date.now(), ...s,
      x: 35 + Math.random() * 30, y: 30 + Math.random() * 30,
      scale: 0.7, rotate: -5 + Math.random() * 10
    }]);
    setView("canvas");
  };

  const onSaveTemplate = (s) => {
    const name = prompt("起一个名字（4字以内）：", s.text);
    if (!name) return;
    setLibrary(prev => [...prev, { ...s, name }]);
    toast("已加入纸条谱");
  };

  return (
    <div className="app">
      <Topbar
        view={view}
        setView={setView}
        onOpenApiKey={() => setShowApiModal(true)}
        onExport={() => onExport()}
        apiKey={apiKey}
      />
      {view === "edit" && (
        <EditView
          strip={strip}
          set={set}
          onAddToCanvas={onAddToCanvas}
          onSaveTemplate={onSaveTemplate}
          onExport={onExport}
          onCopy={onCopy}
        />
      )}
      {view === "ai" && (
        <AiView
          strip={strip}
          set={set}
          apiKey={apiKey}
          onOpenApiKey={() => setShowApiModal(true)}
          library={library}
          addToLibrary={(t) => setLibrary([...library, t])}
        />
      )}
      {view === "canvas" && (
        <CanvasView
          items={canvasItems}
          setItems={setCanvasItems}
          currentStrip={strip}
          addStripFrom={(t) => setCanvasItems([...canvasItems, t])}
          onExport={onExport}
        />
      )}
      {view === "tpl" && (
        <TemplatesView
          set={set}
          setView={setView}
          library={library}
        />
      )}
      {showApiModal && (
        <ApiKeyModal
          onClose={() => setShowApiModal(false)}
          apiKey={apiKey}
          setApiKey={setApiKey}
        />
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="界面 · UI tone">
          <TweakColor
            label="主色"
            value={t.accent}
            options={["#c4453a", "#9c2f25", "#b08a3e", "#4a6b5e", "#34465a", "#1a1612"]}
            onChange={(v) => setTweak({ accent: v })}
          />
          <TweakRadio
            label="密度"
            value={t.uiDensity}
            options={["紧凑", "正常", "宽松"]}
            onChange={(v) => setTweak({ uiDensity: v })}
          />
          <TweakToggle
            label="舞台暗角"
            value={t.stageVignette}
            onChange={(v) => setTweak({ stageVignette: v })}
          />
        </TweakSection>
        <TweakSection label="默认值 · Defaults">
          <TweakSlider
            label="基准缩放"
            min={0.6} max={1.4} step={0.05}
            value={t.stripScale}
            onChange={(v) => setTweak({ stripScale: v })}
          />
          <TweakRadio
            label="起步视图"
            value={t.introMode}
            options={["编辑", "AI", "谱"]}
            onChange={(v) => {
              setTweak({ introMode: v });
              const m = { "编辑": "edit", "AI": "ai", "谱": "tpl" };
              setView(m[v] || "edit");
            }}
          />
        </TweakSection>
      </TweaksPanel>

      <style>{`
        @keyframes fade { 0%, 100% { opacity: 0.3 } 50% { opacity: 1 } }
        :root { --dan: ${t.accent}; }
        ${t.uiDensity === "紧凑" ? `.left-rail, .right-rail { padding: 12px 12px 60px; } .panel { margin-bottom: 12px; }` : ""}
        ${t.uiDensity === "宽松" ? `.left-rail, .right-rail { padding: 24px 22px 80px; } .panel { margin-bottom: 24px; }` : ""}
        ${t.stageVignette === false ? `.stage-body::before { display: none !important; }` : `
          .stage-body::before {
            content: "";
            position: absolute; inset: 0;
            background: radial-gradient(closest-side, transparent 60%, rgba(0,0,0,0.18));
            pointer-events: none;
            z-index: 1;
          }
        `}
      `}</style>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
