// Small reusable atoms (Tailwind-classed)

const cn = (...xs) => xs.filter(Boolean).join(" ");

// Dot indicator (space color etc)
function Dot({ color = "#8B7F6C", size = 8, className = "", style }) {
  return (
    <span
      aria-hidden
      className={cn("inline-block rounded-full shrink-0", className)}
      style={{ width: size, height: size, background: color, ...style }}
    />
  );
}

// Glowing pulse dot
function Pulse({ tone = "ok", className = "" }) {
  return <span aria-hidden className={cn("pulse-dot", tone, className)} />;
}

// Keyboard chip
function Kbd({ children, className = "" }) {
  return <span className={cn("kbd shrink-0", className)}>{children}</span>;
}

// Soft pill
function Pill({ tone = "default", children, className = "" }) {
  const toneCls = {
    ok:    "bg-okSoft text-[#3E7C5C]",
    lock:  "bg-lockBg  text-[#7A6F5C]",
    default:"bg-chipBg text-[#6B6253]",
  }[tone] || "bg-chipBg text-[#6B6253]";
  return (
    <span className={cn("inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[11.5px] font-medium", toneCls, className)}>
      {children}
    </span>
  );
}

// Generic button
function Btn({ kind = "ghost", size = "md", className = "", children, ...rest }) {
  const k = {
    primary: "bg-ink text-cream hover:bg-black",
    ghost:   "border border-rule bg-paper text-[#5C5347] hover:bg-cream",
    soft:    "bg-chipBg text-[#5C5347] hover:bg-chipBg/80",
    danger:  "bg-[#C5524C] text-white hover:bg-[#a8443f]",
    plain:   "text-mute hover:text-ink",
  }[kind];
  const sz = {
    sm: "px-2.5 py-1 text-[12px] rounded-md",
    md: "px-3 py-1.5 text-[13px] rounded-md",
    lg: "px-4 py-2 text-[14px] rounded-lg font-medium",
  }[size];
  return (
    <button {...rest} className={cn("font-medium transition-colors disabled:opacity-50", k, sz, className)}>
      {children}
    </button>
  );
}

// Avatar (single char)
function Avatar({ char = "준", size = 26 }) {
  return (
    <div
      className="rounded-full bg-[#D5CAB1] text-[#5C5347] font-semibold flex items-center justify-center shrink-0"
      style={{ width: size, height: size, fontSize: size * 0.42 }}
    >{char}</div>
  );
}

// Crumb separator
function Crumb({ children }) {
  return <span className="flex items-center gap-2 text-[13px] text-mute min-w-0">{children}</span>;
}

// Auto-resizing chat composer textarea
// Features: @doc mentions, attach menu, model picker
function ChatComposer({ placeholder, onSend, hint, disabled }) {
  const { state, getSpace } = (typeof useApp !== "undefined") ? useApp() : { state: { docs: [], spaces: [] }, getSpace: () => null };
  const [value, setValue] = React.useState("");
  const ref = React.useRef();
  const [mention, setMention] = React.useState(null); // { query, startIdx } | null
  const [attachOpen, setAttachOpen] = React.useState(false);
  const [activeIdx, setActiveIdx] = React.useState(0);
  const [modelOpen, setModelOpen] = React.useState(false);
  const [model, setModel] = React.useState(() => {
    try { return localStorage.getItem("mywiki:model") || "haiku"; } catch { return "haiku"; }
  });
  const setModelPersist = (m) => {
    setModel(m); setModelOpen(false);
    try { localStorage.setItem("mywiki:model", m); } catch {}
  };
  const MODELS = [
    { id: "haiku",  name: "Claude Haiku 4.5",  hint: "빠른 응답 · 짧은 정리에 좋음", short: "Haiku" },
    { id: "sonnet", name: "Claude Sonnet 4.5", hint: "균형 · 기본 추천",            short: "Sonnet" },
    { id: "opus",   name: "Claude Opus 4.1",   hint: "깊은 분석 · 긴 글에 좋음",     short: "Opus" },
  ];
  const currentModel = MODELS.find(m => m.id === model) || MODELS[0];

  React.useEffect(() => {
    if (!ref.current) return;
    ref.current.style.height = "auto";
    ref.current.style.height = Math.min(ref.current.scrollHeight, 180) + "px";
  }, [value]);

  // Mention detection — find "@<word>" right before the cursor
  const detectMention = (v, caret) => {
    // walk back from caret while seeing non-space, non-@ chars
    let i = caret - 1;
    while (i >= 0 && v[i] !== "@" && v[i] !== " " && v[i] !== "\n") i--;
    if (i < 0 || v[i] !== "@") return null;
    // ensure @ is at start or preceded by whitespace
    if (i > 0 && !/\s/.test(v[i - 1])) return null;
    const query = v.slice(i + 1, caret);
    return { query, startIdx: i };
  };

  const onChange = (e) => {
    const v = e.target.value;
    setValue(v);
    const caret = e.target.selectionStart;
    const m = detectMention(v, caret);
    setMention(m);
    setActiveIdx(0);
  };

  // Filtered doc list for mention popover
  const mentionDocs = React.useMemo(() => {
    if (!mention) return [];
    const q = mention.query.toLowerCase();
    return state.docs.filter(d => !q || d.title.toLowerCase().includes(q)).slice(0, 8);
  }, [mention, state.docs]);

  const insertMention = (doc) => {
    if (!mention || !doc) return;
    const inserted = `[${doc.title}](#doc:${doc.id}) `;
    const next = value.slice(0, mention.startIdx) + inserted + value.slice(mention.startIdx + 1 + mention.query.length);
    setValue(next);
    setMention(null);
    // restore caret
    requestAnimationFrame(() => {
      const ta = ref.current; if (!ta) return;
      ta.focus();
      const caret = mention.startIdx + inserted.length;
      ta.setSelectionRange(caret, caret);
    });
  };

  const submit = () => {
    const t = value.trim();
    if (!t || disabled) return;
    onSend?.(t);
    setValue("");
    setMention(null);
  };

  const onKeyDown = (e) => {
    // mention popover navigation
    if (mention && mentionDocs.length > 0) {
      if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx((activeIdx + 1) % mentionDocs.length); return; }
      if (e.key === "ArrowUp")   { e.preventDefault(); setActiveIdx((activeIdx - 1 + mentionDocs.length) % mentionDocs.length); return; }
      if (e.key === "Escape")    { e.preventDefault(); setMention(null); return; }
      if ((e.key === "Enter" || e.key === "Tab") && !e.nativeEvent.isComposing) {
        e.preventDefault();
        insertMention(mentionDocs[activeIdx]);
        return;
      }
    }
    if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
      e.preventDefault();
      submit();
    }
  };

  const hasText = !!value.trim();

  return (
    <div className="px-3 sm:px-6 pb-4 pt-2 shrink-0">
      <div className="mx-auto w-full max-w-[700px] relative">

        {/* Mention popover */}
        {mention && mentionDocs.length > 0 && (
          <div className="absolute bottom-full left-2 right-2 sm:left-0 sm:right-auto sm:w-[420px] mb-2 bg-paper border border-rule rounded-xl shadow-pop p-1.5 z-30">
            <div className="px-2.5 pt-1.5 pb-1 text-[10.5px] text-mute uppercase tracking-[0.08em] font-medium flex items-center justify-between">
              <span>문서 언급</span>
              <span className="normal-case text-dim">↑↓ · ⏎ 선택</span>
            </div>
            <div className="max-h-[240px] overflow-y-auto scroll-paper">
              {mentionDocs.map((d, i) => {
                const sp = getSpace(d.spaceId);
                return (
                  <button
                    key={d.id}
                    onMouseDown={(e) => { e.preventDefault(); insertMention(d); }}
                    onMouseEnter={() => setActiveIdx(i)}
                    className={cn(
                      "w-full text-left flex items-center gap-2 px-2.5 py-1.5 rounded-md",
                      i === activeIdx ? "bg-chipBg" : "hover:bg-cream"
                    )}
                  >
                    <Dot color={sp?.dot} size={7} />
                    <span className="text-[12px] text-mute shrink-0">{sp?.name}</span>
                    <span className="text-[13.5px] text-ink truncate flex-1">{d.title}</span>
                  </button>
                );
              })}
            </div>
          </div>
        )}

        {/* Attach popover */}
        {attachOpen && (
          <div className="absolute bottom-full left-3 mb-2 bg-paper border border-rule rounded-xl shadow-pop py-1.5 z-30 w-[220px]"
               onMouseLeave={() => setAttachOpen(false)}>
            <AttachItem icon="🖼️" label="이미지 첨부" hint="곧 지원" disabled />
            <AttachItem icon="🔗" label="URL 첨부"   hint="곧 지원" disabled />
            <div className="border-t border-ruleSoft my-1" />
            <AttachItem icon="↗"  label="문서 언급 (@)" hint="채팅에 @ 입력" onClick={() => {
              setAttachOpen(false);
              const ta = ref.current; if (!ta) return;
              ta.focus();
              const next = value + (value && !value.endsWith(" ") ? " @" : "@");
              setValue(next);
              setTimeout(() => {
                ta.setSelectionRange(next.length, next.length);
                setMention({ query: "", startIdx: next.length - 1 });
              }, 0);
            }} />
          </div>
        )}

        <div className="bg-paper border border-rule rounded-2xl shadow-paper p-3">
          <textarea
            ref={ref}
            rows={1}
            value={value}
            disabled={disabled}
            onChange={onChange}
            placeholder={placeholder}
            onKeyDown={onKeyDown}
            onBlur={() => setTimeout(() => setMention(null), 100)}
            className="w-full bg-transparent placeholder:text-[#B6AB94] text-[14.5px] leading-[1.55] text-ink2 outline-none"
          />
          <div className="mt-2 flex items-center justify-between text-[11.5px] text-dim gap-2">
            <div className="flex items-center gap-1.5 min-w-0">
              <button
                onClick={() => { setAttachOpen(o => !o); setModelOpen(false); }}
                aria-label="첨부"
                className={cn(
                  "w-7 h-7 rounded-full inline-flex items-center justify-center text-mute hover:text-ink hover:bg-cream transition shrink-0",
                  attachOpen && "bg-chipBg text-ink"
                )}
              >
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 3v8M3 7h8" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
              </button>
              <div className="relative">
                <button
                  onClick={() => { setModelOpen(o => !o); setAttachOpen(false); }}
                  className={cn(
                    "inline-flex items-center gap-1 px-2 py-0.5 rounded-md hover:bg-cream hover:text-ink transition truncate",
                    modelOpen && "bg-chipBg text-ink"
                  )}
                >
                  <span className="truncate">
                    <span className="sm:hidden">{currentModel.short}</span>
                    <span className="hidden sm:inline">대화 모델 · {currentModel.name}</span>
                  </span>
                  <span className="text-[9px] opacity-70">▾</span>
                </button>
                {modelOpen && (
                  <div className="absolute bottom-full left-0 mb-2 w-[260px] bg-paper border border-rule rounded-xl shadow-pop p-1.5 z-40">
                    <div className="px-2 pt-1 pb-1.5 text-[10.5px] text-mute uppercase tracking-[0.08em] font-medium">대화 모델</div>
                    {MODELS.map(m => (
                      <button
                        key={m.id}
                        onClick={() => setModelPersist(m.id)}
                        className={cn(
                          "w-full text-left px-2.5 py-2 rounded-md hover:bg-cream flex items-start gap-2",
                          m.id === model && "bg-chipBg"
                        )}
                      >
                        <span className={cn("w-3 mt-1 text-[11px]", m.id === model ? "text-[#3E7C5C]" : "text-transparent")}>●</span>
                        <span className="flex-1 min-w-0">
                          <span className={cn("block text-[13px] truncate", m.id === model ? "text-ink font-semibold" : "text-ink2 font-medium")}>{m.name}</span>
                          <span className="block text-[11px] text-dim mt-0.5">{m.hint}</span>
                        </span>
                      </button>
                    ))}
                  </div>
                )}
              </div>
            </div>
            <div className="flex items-center gap-2 shrink-0">
              {hint}
              <button
                onClick={submit}
                disabled={disabled || !hasText}
                aria-label="보내기"
                className={cn(
                  "w-7 h-7 rounded-full inline-flex items-center justify-center text-cream transition",
                  hasText ? "bg-ink hover:opacity-90" : "bg-[#C9BFA5]"
                )}
              >↑</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function AttachItem({ icon, label, hint, onClick, disabled }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={cn(
        "w-full flex items-center gap-2.5 px-3 py-2 text-left text-[13px] text-ink",
        disabled ? "opacity-60 cursor-default" : "hover:bg-cream"
      )}
    >
      <span className="w-5 inline-flex items-center justify-center text-[14px]">{icon}</span>
      <span className="flex-1">{label}</span>
      {hint && <span className="text-[10.5px] text-dim">{hint}</span>}
    </button>
  );
}

// Modal scaffold
function Modal({ open, onClose, children, size = "md", scrim = true }) {
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, [open]);
  if (!open) return null;
  const wmap = { sm: "max-w-[400px]", md: "max-w-[520px]", lg: "max-w-[720px]" };
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-6 om-fadeIn"
         onClick={() => scrim && onClose?.()}>
      <div className="absolute inset-0 bg-ink/30 backdrop-blur-[2px]" />
      <div
        onClick={(e) => e.stopPropagation()}
        className={cn(
          "relative bg-paper border border-rule rounded-2xl shadow-pop w-full overflow-hidden om-slideUp",
          wmap[size]
        )}>
        {children}
      </div>
    </div>
  );
}

// Bottom sheet (mobile)
function Sheet({ open, onClose, children, title }) {
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, [open]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-end om-fadeIn" onClick={onClose}>
      <div className="absolute inset-0 bg-ink/30 backdrop-blur-[1px]" />
      <div onClick={e => e.stopPropagation()}
           className="relative w-full bg-paper rounded-t-3xl border-t border-rule shadow-sheet max-h-[85vh] overflow-hidden flex flex-col om-sheet">
        <div className="pt-2.5 pb-1 flex justify-center"><div className="h-1 w-9 rounded-full bg-[#D9CDA9]" /></div>
        {title && (
          <div className="px-4 pb-2 text-[15px] font-semibold text-ink">{title}</div>
        )}
        <div className="overflow-auto scroll-paper">{children}</div>
      </div>
    </div>
  );
}

// Skeleton bar (typing indicator)
function TypingBubble() {
  return (
    <div className="self-start text-body text-[14.5px] leading-[1.65] inline-flex items-center gap-1">
      <span className="w-1.5 h-1.5 rounded-full bg-[#B6AB94] om-shimmer" style={{ animationDelay: "0ms"   }} />
      <span className="w-1.5 h-1.5 rounded-full bg-[#B6AB94] om-shimmer" style={{ animationDelay: "200ms" }} />
      <span className="w-1.5 h-1.5 rounded-full bg-[#B6AB94] om-shimmer" style={{ animationDelay: "400ms" }} />
    </div>
  );
}

Object.assign(window, {
  cn, Dot, Pulse, Kbd, Pill, Btn, Avatar, Crumb, ChatComposer, Modal, Sheet, TypingBubble,
});
