/* global React */
// ============================================================
// PandaKit · Shared UI primitives
// ============================================================

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

// ---- Button ---------------------------------------------------
const Button = ({
  variant = "primary",   // primary | secondary | ghost | danger | icon
  size = "md",           // sm | md | lg
  leftIcon, rightIcon,
  children,
  disabled,
  loading,
  active,
  className,
  style,
  ...rest
}) => {
  const base = {
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    gap: 8,
    fontWeight: 600,
    border: "1px solid transparent",
    borderRadius: 12,
    cursor: disabled ? "not-allowed" : "pointer",
    transition: "transform 180ms var(--ease), background 180ms var(--ease), border-color 180ms var(--ease), box-shadow 180ms var(--ease), color 180ms var(--ease)",
    whiteSpace: "nowrap",
    userSelect: "none",
    opacity: disabled ? 0.45 : 1,
  };
  const sizes = {
    sm: { height: 32, padding: "0 12px", fontSize: 13 },
    md: { height: 40, padding: "0 16px", fontSize: 14 },
    lg: { height: 48, padding: "0 22px", fontSize: 15 },
  };
  const variants = {
    primary: {
      background: active ? "var(--primary-deep)" : "var(--primary)",
      color: "#231C0E",
      borderColor: "var(--primary-deep)",
      boxShadow: "0 1px 0 rgba(255,255,255,0.4) inset, 0 2px 6px rgba(245,197,106,0.35)",
    },
    secondary: {
      background: "var(--card)",
      color: "var(--text)",
      borderColor: "var(--border-strong)",
    },
    ghost: {
      background: "transparent",
      color: "var(--text-soft)",
      borderColor: "transparent",
    },
    danger: {
      background: "#fff",
      color: "#B85454",
      borderColor: "#EFC8C8",
    },
    icon: {
      background: "transparent",
      color: "var(--text-soft)",
      borderColor: "transparent",
      width: 36, height: 36, padding: 0,
    },
  };
  const merged = { ...base, ...sizes[size], ...variants[variant], ...(style || {}) };
  if (variant === "icon") merged.borderRadius = 10;

  return (
    <button
      className={`pk-focus-ring ${className || ""}`}
      style={merged}
      disabled={disabled || loading}
      onMouseEnter={(e) => { if (!disabled) e.currentTarget.style.transform = "translateY(-1px)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; }}
      onMouseDown={(e) => { if (!disabled) e.currentTarget.style.transform = "translateY(0) scale(0.98)"; }}
      onMouseUp={(e) => { if (!disabled) e.currentTarget.style.transform = "translateY(-1px)"; }}
      {...rest}
    >
      {loading ? <Spinner size={14} /> : leftIcon}
      {children}
      {rightIcon}
    </button>
  );
};

// ---- IconButton (square, ghost-by-default) -------------------
const IconButton = ({ children, label, active, onClick, size = 38, ...rest }) => (
  <button
    aria-label={label}
    onClick={onClick}
    className="pk-focus-ring"
    style={{
      width: size, height: size, display: "inline-flex", alignItems: "center", justifyContent: "center",
      borderRadius: 10,
      background: active ? "var(--primary-soft)" : "transparent",
      color: active ? "var(--text)" : "var(--text-soft)",
      border: "1px solid " + (active ? "var(--primary)" : "transparent"),
      cursor: "pointer",
      transition: "all 160ms var(--ease)",
    }}
    onMouseEnter={(e) => { if (!active) { e.currentTarget.style.background = "var(--bg-soft)"; e.currentTarget.style.color = "var(--text)"; } }}
    onMouseLeave={(e) => { if (!active) { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--text-soft)"; } }}
    {...rest}
  >
    {children}
  </button>
);

// ---- Spinner --------------------------------------------------
const Spinner = ({ size = 18, color = "currentColor" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ animation: "pk-spin 0.9s linear infinite" }}>
    <circle cx="12" cy="12" r="10" fill="none" stroke={color} strokeOpacity="0.18" strokeWidth="3" />
    <path d="M22 12a10 10 0 0 0-10-10" fill="none" stroke={color} strokeWidth="3" strokeLinecap="round" />
  </svg>
);

// ---- Input ----------------------------------------------------
const Input = ({
  value, onChange, placeholder,
  prefixIcon, suffixIcon,
  clearable, error, multiline, rows = 3,
  size = "md", style, className, ...rest
}) => {
  const [focus, setFocus] = useState(false);
  const heights = { sm: 34, md: 40, lg: 46 };
  const wrapStyle = {
    display: "flex", alignItems: multiline ? "flex-start" : "center",
    background: "var(--card)",
    border: `1px solid ${error ? "#E0A0A0" : focus ? "var(--primary)" : "var(--border-strong)"}`,
    borderRadius: 12,
    padding: multiline ? "10px 12px" : "0 12px",
    minHeight: heights[size],
    transition: "border-color 160ms var(--ease), box-shadow 160ms var(--ease)",
    boxShadow: focus ? "0 0 0 3px rgba(245,197,106,0.18)" : "none",
    ...style,
  };
  const inputStyle = {
    flex: 1, border: "none", outline: "none", background: "transparent",
    color: "var(--text)",
    fontSize: 14, fontFamily: "inherit",
    padding: 0,
    height: multiline ? "auto" : "100%",
    resize: multiline ? "vertical" : "none",
  };
  const Tag = multiline ? "textarea" : "input";
  return (
    <div>
      <div className={className} style={wrapStyle}>
        {prefixIcon && <span style={{ display: "inline-flex", marginRight: 8, color: "var(--text-soft)" }}>{prefixIcon}</span>}
        <Tag
          value={value}
          onChange={onChange}
          placeholder={placeholder}
          rows={multiline ? rows : undefined}
          onFocus={() => setFocus(true)}
          onBlur={() => setFocus(false)}
          style={inputStyle}
          {...rest}
        />
        {clearable && value && !multiline && (
          <button
            onClick={() => onChange?.({ target: { value: "" } })}
            aria-label="clear"
            style={{ border: "none", background: "transparent", color: "var(--text-faint)", cursor: "pointer", padding: 4, display: "inline-flex" }}
          >
            <Icon name="x" size={14} />
          </button>
        )}
        {suffixIcon && <span style={{ display: "inline-flex", marginLeft: 8, color: "var(--text-soft)" }}>{suffixIcon}</span>}
      </div>
      {error && <div style={{ fontSize: 12, color: "#B85454", marginTop: 6 }}>{error}</div>}
    </div>
  );
};

// ---- Choice card (round / square) ----------------------------
// 删 title={label} 防浏览器原生 tooltip 跟 Tooltip 组件并存
const ChoiceCard = ({
  shape = "round", // round | square
  selected,
  onClick,
  children,
  label,
  size = 64,
  className,
}) => (
  <button
    onClick={onClick}
    aria-pressed={selected}
    aria-label={label}
    className={`pk-focus-ring ${className || ""}`}
    style={{
      width: size, height: size,
      borderRadius: shape === "round" ? "50%" : 16,
      background: selected ? "var(--primary-soft)" : "var(--card)",
      border: `2px solid ${selected ? "var(--primary)" : "var(--border)"}`,
      cursor: "pointer",
      display: "flex", alignItems: "center", justifyContent: "center",
      transition: "all 180ms var(--ease)",
      transform: selected ? "translateY(-2px) scale(1.02)" : "translateY(0)",
      boxShadow: selected ? "0 4px 14px rgba(245,197,106,0.32)" : "var(--shadow-sm)",
      padding: 0,
      flexShrink: 0,
      position: "relative",
      overflow: "hidden",
    }}
    onMouseEnter={(e) => { if (!selected) e.currentTarget.style.transform = "translateY(-2px)"; }}
    onMouseLeave={(e) => { if (!selected) e.currentTarget.style.transform = "translateY(0)"; }}
  >
    {children}
  </button>
);

// ---- Tabs (top underline) ------------------------------------
const Tabs = ({ value, onChange, items, variant = "underline", className }) => {
  if (variant === "side") {
    return (
      <div className={className} style={{ display: "flex", flexDirection: "column", gap: 4, padding: 8, background: "var(--bg-soft)", borderRadius: 14 }}>
        {items.map((it) => {
          const active = it.value === value;
          return (
            <button
              key={it.value}
              onClick={() => onChange(it.value)}
              className="pk-focus-ring"
              style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "10px 12px",
                borderRadius: 10,
                background: active ? "var(--card)" : "transparent",
                border: "none",
                color: active ? "var(--text)" : "var(--text-soft)",
                fontWeight: active ? 600 : 500,
                fontSize: 14,
                cursor: "pointer",
                textAlign: "left",
                boxShadow: active ? "var(--shadow-sm)" : "none",
                transition: "all 160ms var(--ease)",
              }}
            >
              {it.icon}
              <span>{it.label}</span>
            </button>
          );
        })}
      </div>
    );
  }
  if (variant === "pill") {
    return (
      <div className={className} style={{ display: "inline-flex", gap: 4, padding: 4, background: "var(--bg-soft)", borderRadius: 999 }}>
        {items.map((it) => {
          const active = it.value === value;
          return (
            <button
              key={it.value}
              onClick={() => onChange(it.value)}
              className="pk-focus-ring"
              style={{
                padding: "6px 14px",
                borderRadius: 999,
                border: "none",
                background: active ? "var(--card)" : "transparent",
                color: active ? "var(--text)" : "var(--text-soft)",
                // fontWeight 锁定 600 防选中切换时字宽变化导致 layout 抖动
                fontWeight: 600,
                fontSize: 13,
                cursor: "pointer",
                boxShadow: active ? "var(--shadow-sm)" : "none",
                transition: "background 160ms var(--ease), color 160ms var(--ease), box-shadow 160ms var(--ease)",
              }}
            >
              {it.label}
            </button>
          );
        })}
      </div>
    );
  }
  // underline (default) — 用 nav tab，用 minWidth 锁宽 + fontWeight 锁定避免中英切换/选中抖动
  return (
    <div className={className} style={{ display: "inline-flex", gap: 28, position: "relative" }}>
      {items.map((it) => {
        const active = it.value === value;
        return (
          <button
            key={it.value}
            onClick={() => onChange(it.value)}
            className="pk-focus-ring"
            style={{
              border: "none", background: "transparent", padding: "10px 0",
              color: active ? "var(--text)" : "var(--text-soft)",
              fontWeight: 600,  // 锁定，不在 active 时变粗
              fontSize: 15,
              cursor: "pointer",
              position: "relative",
              transition: "color 160ms var(--ease)",
              minWidth: 64,
            }}
          >
            {it.label}
            <span style={{
              position: "absolute", left: "50%", bottom: 4,
              transform: `translateX(-50%) scaleX(${active ? 1 : 0})`,
              transformOrigin: "center",
              transition: "transform 220ms var(--ease)",
              width: 22, height: 2, borderRadius: 2,
              background: "var(--primary)",
            }} />
          </button>
        );
      })}
    </div>
  );
};

// ---- Dropdown -------------------------------------------------
const Dropdown = ({ value, onChange, options, searchable, placeholder = "选择…", direction = "auto", style, renderOption }) => {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState("");
  const [up, setUp] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    const onDoc = (e) => { if (!ref.current?.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);
  useEffect(() => {
    if (!open || direction !== "auto") return;
    const r = ref.current?.getBoundingClientRect();
    if (!r) return;
    const spaceBelow = window.innerHeight - r.bottom;
    const spaceAbove = r.top;
    // Open up only if below is cramped AND above has more room
    setUp(spaceBelow < 260 && spaceAbove > spaceBelow);
  }, [open, direction]);
  const goUp = direction === "up" || (direction === "auto" && up);
  const current = options.find((o) => o.value === value);
  const filtered = options.filter((o) => !q || (o.label || "").toLowerCase().includes(q.toLowerCase()));
  return (
    <div ref={ref} style={{ position: "relative", ...style }}>
      <button
        onClick={() => setOpen(!open)}
        className="pk-focus-ring"
        style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          height: 38, padding: "0 12px",
          background: "var(--card)", color: "var(--text)",
          border: "1px solid var(--border-strong)", borderRadius: 12,
          cursor: "pointer", fontSize: 14, fontWeight: 500,
          minWidth: 120, justifyContent: "space-between",
          width: "100%",
        }}
      >
        <span style={{ color: current ? "var(--text)" : "var(--text-faint)" }}>{current?.label || placeholder}</span>
        <Icon name="chevronDown" size={16} />
      </button>
      {open && (
        <div style={{
          position: "absolute",
          [goUp ? "bottom" : "top"]: "calc(100% + 6px)", left: 0, right: 0,
          background: "var(--card)", border: "1px solid var(--border)",
          borderRadius: 12, boxShadow: "var(--shadow-md)",
          zIndex: 100, padding: 6, minWidth: 160,
          animation: "pk-fade-up 160ms var(--ease)",
        }}>
          {searchable && (
            <div style={{ padding: 4 }}>
              <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder="搜索…" size="sm" prefixIcon={<Icon name="search" size={14} />} />
            </div>
          )}
          <div style={{ maxHeight: 240, overflowY: "auto" }} className="pk-rail">
            {filtered.map((o) => (
              <button
                key={o.value}
                onClick={() => { onChange?.(o.value); setOpen(false); setQ(""); }}
                style={{
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                  width: "100%", padding: "8px 10px",
                  border: "none", background: o.value === value ? "var(--bg-soft)" : "transparent",
                  borderRadius: 8, fontSize: 14, color: "var(--text)",
                  cursor: "pointer", textAlign: "left",
                }}
                onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-soft)"}
                onMouseLeave={(e) => e.currentTarget.style.background = o.value === value ? "var(--bg-soft)" : "transparent"}
              >
                <span style={{ display: "flex", alignItems: "center", flex: 1, minWidth: 0 }}>
                  {o.render || o.label}
                </span>
                {o.value === value && <Icon name="check" size={14} />}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

// ---- Slider (single + ticks) ---------------------------------
const Slider = ({
  value, onChange, min = 0, max = 100, step = 1,
  leftLabel, rightLabel, ticks,
  style,
}) => {
  const pct = ((value - min) / (max - min)) * 100;
  return (
    <div style={{ width: "100%", ...style }}>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, color: "var(--text-soft)", marginBottom: 6 }}>
        <span>{leftLabel}</span>
        <span>{rightLabel}</span>
      </div>
      <div style={{ position: "relative", height: 22, display: "flex", alignItems: "center" }}>
        <div style={{ position: "absolute", left: 0, right: 0, height: 6, borderRadius: 999, background: "var(--bg-soft)" }} />
        <div style={{ position: "absolute", left: 0, height: 6, borderRadius: 999, background: "var(--primary)", width: `${pct}%` }} />
        {ticks && Array.from({ length: ticks }).map((_, i) => (
          <span key={i} style={{
            position: "absolute", left: `${(i / (ticks - 1)) * 100}%`,
            width: 2, height: 8, borderRadius: 1, background: "var(--border-strong)",
            transform: "translateX(-1px)",
          }} />
        ))}
        <input
          type="range" min={min} max={max} step={step} value={value}
          onChange={(e) => onChange(Number(e.target.value))}
          style={{
            position: "absolute", left: 0, right: 0, top: 0, height: 22,
            opacity: 0, margin: 0, cursor: "pointer", width: "100%",
          }}
        />
        <span style={{
          position: "absolute", left: `calc(${pct}% - 9px)`,
          width: 18, height: 18, borderRadius: "50%",
          background: "#fff", border: "2px solid var(--primary)",
          boxShadow: "var(--shadow-sm)",
          pointerEvents: "none",
          transition: "transform 120ms var(--ease)",
        }} />
      </div>
    </div>
  );
};

// ---- Toast ----------------------------------------------------
const ToastContext = React.createContext({ push: () => {} });

const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((msg, kind = "info") => {
    const id = Math.random().toString(36).slice(2);
    setToasts((ts) => [...ts, { id, msg, kind }]);
    setTimeout(() => setToasts((ts) => ts.filter((t) => t.id !== id)), 2400);
  }, []);
  const value = useMemo(() => ({ push }), [push]);
  return (
    <ToastContext.Provider value={value}>
      {children}
      <div style={{
        position: "fixed", top: 24, right: 24, zIndex: 1000,
        display: "flex", flexDirection: "column", gap: 8, alignItems: "flex-end",
        pointerEvents: "none",
      }}>
        {toasts.map((t) => (
          <div key={t.id} style={{
            background: "var(--card)", border: "1px solid var(--border)",
            borderRadius: 12, padding: "10px 14px",
            boxShadow: "var(--shadow-md)",
            display: "flex", alignItems: "center", gap: 10,
            color: "var(--text)", fontSize: 14, fontWeight: 500,
            animation: "pk-toast-in 220ms var(--ease)",
            pointerEvents: "auto",
          }}>
            <span style={{
              width: 8, height: 8, borderRadius: "50%",
              background: t.kind === "success" ? "var(--accent)" : t.kind === "error" ? "#E97B7B" : t.kind === "warn" ? "var(--primary-deep)" : "var(--text-soft)",
            }} />
            {t.msg}
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
};

const useToast = () => React.useContext(ToastContext);

// ---- Modal & BottomSheet --------------------------------------
const Modal = ({ open, onClose, title, children, footer, width = 480 }) => {
  if (!open) return null;
  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 800,
        background: "rgba(40,34,28,0.32)", backdropFilter: "blur(2px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 24,
        animation: "pk-fade-in 160ms var(--ease)",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: "var(--card)", borderRadius: 18,
          width: "100%", maxWidth: width,
          boxShadow: "var(--shadow-lg)",
          animation: "pk-fade-up 220ms var(--ease)",
          overflow: "hidden",
        }}
      >
        {title && (
          <div style={{ padding: "18px 22px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
            <h3 style={{ margin: 0, fontSize: 16, fontWeight: 700 }}>{title}</h3>
            <IconButton label="close" onClick={onClose}><Icon name="x" /></IconButton>
          </div>
        )}
        <div style={{ padding: 22 }}>{children}</div>
        {footer && (
          <div style={{ padding: "14px 22px", borderTop: "1px solid var(--border)", display: "flex", justifyContent: "flex-end", gap: 8 }}>
            {footer}
          </div>
        )}
      </div>
    </div>
  );
};

// ---- Tooltip --------------------------------------------------
// 修漂移：用 flex 居中浮层不依赖 transform: translateX(-50%)
//   （旧版 inline transform 被 pk-fade-up keyframes 的 transform: translateY 覆盖）
// 移动端：touch 设备 mouseEnter 触发但无 mouseLeave → tooltip 卡住。
//   className="pk-tooltip" + CSS @media (hover: none) display:none 隐藏。
const Tooltip = ({ children, content, side = "top", variant = "dark" }) => {
  const [show, setShow] = useState(false);
  const colors = variant === "dark"
    ? { bg: "#2B2826", color: "#fff", border: "transparent" }
    : { bg: "#fff", color: "var(--text)", border: "1px solid var(--border)" };
  const isTop = side === "top";
  return (
    <span style={{ position: "relative", display: "inline-flex" }}
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
      onFocus={() => setShow(true)} onBlur={() => setShow(false)}
    >
      {children}
      {show && (
        // 外层 wrapper 横跨整个父宽并 flex 居中（不用 transform）
        <span className="pk-tooltip" style={{
          position: "absolute",
          ...(isTop ? { bottom: "100%", marginBottom: 8 } : { top: "100%", marginTop: 8 }),
          left: 0, right: 0,
          display: "flex", justifyContent: "center",
          pointerEvents: "none",
          zIndex: 60,
        }}>
          <span style={{
            background: colors.bg, color: colors.color, border: colors.border,
            padding: "5px 9px", borderRadius: 8, fontSize: 12, fontWeight: 500,
            whiteSpace: "nowrap",
            boxShadow: "var(--shadow-sm)",
            animation: "pk-fade-in 140ms var(--ease)",  // 只 opacity 不 transform，不会冲突
          }}>
            {content}
          </span>
        </span>
      )}
    </span>
  );
};

// ---- HeartButton (with pulse + particles) --------------------
const HeartButton = ({ active, onChange, size = 22, label }) => {
  const [bursting, setBursting] = useState(0);
  const handle = () => {
    if (!active) setBursting((n) => n + 1);
    onChange?.(!active);
  };
  return (
    <button
      onClick={handle}
      aria-pressed={active}
      aria-label={label}
      style={{
        position: "relative",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        width: size + 14, height: size + 14,
        background: "transparent", border: "none", cursor: "pointer",
        outline: "none", borderRadius: 999,
      }}
      onFocus={(e) => e.currentTarget.style.boxShadow = "none"}
    >
      <span style={{
        display: "inline-flex",
        color: active ? "var(--heart)" : "var(--text-soft)",
        animation: bursting ? "pk-heart-pulse 520ms var(--ease)" : "none",
      }}>
        <svg width={size} height={size} viewBox="0 0 24 24" fill={active ? "currentColor" : "none"} stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
          <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
        </svg>
      </span>
      {/* particles */}
      {bursting > 0 && (
        <span key={bursting} aria-hidden style={{ position: "absolute", inset: 0, pointerEvents: "none" }}>
          {Array.from({ length: 6 }).map((_, i) => {
            const a = (i / 6) * Math.PI * 2;
            const dx = Math.cos(a) * 22;
            const dy = Math.sin(a) * 22;
            return (
              <span key={i} style={{
                position: "absolute", left: "50%", top: "50%",
                width: 5, height: 5, borderRadius: "50%",
                background: "var(--heart)",
                animation: "pk-particle 540ms var(--ease) forwards",
                "--dx": `${dx}px`, "--dy": `${dy}px`,
              }} />
            );
          })}
        </span>
      )}
    </button>
  );
};

// ---- NamePopover (anchored mini-input) -----------------------
const NamePopover = ({ open, onClose, onSave, anchor = "bottom", title, placeholder, initial = "" }) => {
  const i18n = (typeof window !== 'undefined' && window.useI18n) ? window.useI18n() : null;
  const tt = i18n?.t || ((k) => k);
  const [v, setV] = useState(initial);
  const ref = useRef(null);
  useEffect(() => { if (open) setV(initial); }, [open, initial]);
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (!ref.current?.contains(e.target)) onClose?.(); };
    const onKey = (e) => {
      if (e.key === "Escape") onClose?.();
      if (e.key === "Enter") { onSave?.(v.trim()); }
    };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open, v, onSave, onClose]);
  if (!open) return null;
  const isBottom = anchor === "bottom";
  // 修漂移：原 transform: translateX(-50%) 被 pk-pop-in keyframes 的 transform: scale() 完全覆盖
  // → 弹出瞬间 popover 偏右出现再跳回正中。改用 marginLeft: -130（popover 半宽）替代
  return (
    <div
      ref={ref}
      style={{
        position: "absolute",
        ...(isBottom ? { top: "calc(100% + 10px)" } : { bottom: "calc(100% + 10px)" }),
        left: "50%",
        marginLeft: -130, // popover width 260 的一半，居中
        background: "var(--card)", border: "1px solid var(--border)",
        borderRadius: 14, padding: 12,
        boxShadow: "var(--shadow-lg)",
        width: 260, zIndex: 200,
        transformOrigin: isBottom ? "top center" : "bottom center",
        animation: "pk-pop-in 220ms var(--ease)",
      }}
    >
      {/* arrow */}
      <span style={{
        position: "absolute",
        ...(isBottom ? { top: -6 } : { bottom: -6 }),
        left: "calc(50% - 6px)", // 12px 半宽，避免 transform 漂移
        transform: "rotate(45deg)",
        width: 12, height: 12, background: "var(--card)",
        borderTop: isBottom ? "1px solid var(--border)" : "none",
        borderLeft: isBottom ? "1px solid var(--border)" : "none",
        borderRight: isBottom ? "none" : "1px solid var(--border)",
        borderBottom: isBottom ? "none" : "1px solid var(--border)",
      }} />
      {title && <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text-soft)", marginBottom: 8 }}>{title}</div>}
      <Input
        value={v}
        onChange={(e) => setV(e.target.value)}
        placeholder={placeholder}
        size="sm"
        autoFocus
      />
      <div style={{ display: "flex", justifyContent: "space-between", marginTop: 8, gap: 6 }}>
        <button
          onClick={onClose}
          style={{
            background: "transparent", border: "none",
            color: "var(--text-faint)", fontSize: 12,
            cursor: "pointer", padding: "4px 6px",
          }}
        >{tt("ui.popover.skip")}</button>
        <Button size="sm" onClick={() => onSave?.(v.trim())} disabled={!v.trim()}>{tt("ui.popover.save")}</Button>
      </div>
    </div>
  );
};
const PolaroidFrame = ({ tilt = 0, width = 200, caption, children, selected, onClick, onSelectToggle, hoverLift = true }) => {
  const [hover, setHover] = useState(false);
  const lift = hover && hoverLift;
  return (
    <div
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        position: "relative",
        width,
        background: "#FFFFFF",
        padding: "12px 12px 14px 12px",
        borderRadius: 6,
        boxShadow: lift ? "0 12px 28px rgba(40,34,28,0.18)" : "0 2px 6px rgba(40,34,28,0.10)",
        transform: `rotate(${lift ? 0 : tilt}deg) ${lift ? "scale(1.04) translateY(-4px)" : "scale(1)"}`,
        transition: "transform 280ms var(--ease), box-shadow 280ms var(--ease)",
        cursor: onClick ? "pointer" : "default",
        outline: selected ? "2px solid var(--primary)" : "none",
        outlineOffset: 2,
      }}
    >
      <div style={{ borderRadius: 4, overflow: "hidden", background: "var(--bg-soft)" }}>
        {children}
      </div>
      <div style={{
        marginTop: 8, textAlign: "center",
        fontFamily: "Caveat, var(--font-sans)", fontWeight: 600, fontSize: 18,
        color: "var(--text)", minHeight: 22,
      }}>
        {caption}
      </div>
      {onSelectToggle && (
        <button
          onClick={(e) => { e.stopPropagation(); onSelectToggle(); }}
          aria-label="select"
          style={{
            position: "absolute", top: 8, right: 8,
            width: 22, height: 22, borderRadius: "50%",
            background: selected ? "var(--primary)" : "rgba(255,255,255,0.95)",
            border: `2px solid ${selected ? "var(--primary-deep)" : "var(--border-strong)"}`,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            cursor: "pointer", color: "#231C0E",
            transition: "all 160ms var(--ease)",
          }}
        >
          {selected && <Icon name="check" size={12} stroke={2.6} />}
        </button>
      )}
    </div>
  );
};

// ---- StepCard (for export guide) ------------------------------
const StepCard = ({ n, title, body, image }) => (
  <div style={{
    display: "flex", gap: 14, padding: "14px 0",
    borderBottom: "1px dashed var(--border)",
  }}>
    <div style={{
      flexShrink: 0,
      width: 28, height: 28, borderRadius: "50%",
      background: "var(--primary-soft)",
      color: "var(--text)",
      display: "inline-flex", alignItems: "center", justifyContent: "center",
      fontWeight: 700, fontSize: 13,
      border: "1px solid var(--primary)",
    }}>{n}</div>
    <div style={{ flex: 1 }}>
      <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 4 }}>{title}</div>
      <div style={{ fontSize: 13, color: "var(--text-soft)", lineHeight: 1.55 }}>{body}</div>
      {image !== false && (
        <div style={{
          marginTop: 8,
          width: "100%", height: 64, borderRadius: 8,
          background: "linear-gradient(135deg, var(--bg-soft) 0%, #F8F4EC 100%)",
          color: "var(--text-faint)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontSize: 11, fontWeight: 500,
          border: "1px dashed var(--border-strong)",
        }}>示意图 · {n}</div>
      )}
    </div>
  </div>
);

// ---- EmptyState -----------------------------------------------
const EmptyState = ({ title, body, action, illustration }) => (
  <div style={{ textAlign: "center", padding: "60px 24px", color: "var(--text-soft)" }}>
    <div style={{
      width: 88, height: 88, margin: "0 auto 18px",
      borderRadius: "50%", background: "var(--bg-soft)",
      display: "inline-flex", alignItems: "center", justifyContent: "center",
    }}>{illustration || <PandaGlyph size={42} />}</div>
    <div style={{ color: "var(--text)", fontWeight: 600, fontSize: 16 }}>{title}</div>
    {body && <div style={{ marginTop: 8, fontSize: 13, maxWidth: 360, margin: "8px auto 0" }}>{body}</div>}
    {action && <div style={{ marginTop: 20 }}>{action}</div>}
  </div>
);

// ---- Skeleton -------------------------------------------------
const Skeleton = ({ width = "100%", height = 16, rounded = 8, style }) => (
  <div className="pk-skeleton" style={{ width, height, borderRadius: rounded, ...style }} />
);

// ---- Section header -------------------------------------------
const SectionHeader = ({ title, subtitle, right }) => (
  <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 24, flexWrap: "wrap", gap: 12 }}>
    <div>
      <h2 style={{ margin: 0, fontSize: 24, fontWeight: 700, letterSpacing: "-0.01em" }}>{title}</h2>
      {subtitle && <div style={{ marginTop: 6, color: "var(--text-soft)", fontSize: 14 }}>{subtitle}</div>}
    </div>
    {right}
  </div>
);

// ---- Keyboard tip strip --------------------------------------
// className 让 mobile CSS 隐藏（之前 attribute selector 因 inline style 是 bottom:72 不是 24 没 match 上）
const KbdTip = ({ items }) => (
  <div className="pk-kbd-tip" style={{
    position: "fixed", bottom: 72, left: 24, zIndex: 60,
    display: "inline-flex", alignItems: "center", gap: 10,
    background: "rgba(40,34,28,0.85)", color: "#fff",
    padding: "8px 12px", borderRadius: 999, fontSize: 12,
    boxShadow: "var(--shadow-md)",
    backdropFilter: "blur(8px)",
  }}>
    <Icon name="keyboard" size={14} />
    {items.map((it, i) => (
      <React.Fragment key={i}>
        <Kbd>{it.k}</Kbd>
        <span style={{ opacity: 0.75 }}>{it.label}</span>
      </React.Fragment>
    ))}
  </div>
);

const Kbd = ({ children }) => (
  <kbd style={{
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: 11, padding: "1px 6px",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: 4, background: "rgba(255,255,255,0.08)",
    color: "#fff",
  }}>{children}</kbd>
);

// ---- Section transition wrapper -------------------------------
const SectionSwap = ({ k, direction = "right", children }) => (
  <div
    key={k}
    style={{
      animation: `${direction === "right" ? "pk-slide-in-right" : "pk-slide-in-left"} 240ms var(--ease)`,
    }}
  >
    {children}
  </div>
);

// ---- export ---------------------------------------------------
Object.assign(window, {
  Button, IconButton, Spinner, Input, ChoiceCard, Tabs, Dropdown, Slider,
  ToastProvider, useToast, Modal, Tooltip, HeartButton, PolaroidFrame,
  StepCard, EmptyState, Skeleton, SectionHeader, KbdTip, Kbd, SectionSwap,
  NamePopover,
});
