/* cards.jsx — карточки записей/напоминаний и модальные окна. */

/* ============================================================ КАРТОЧКА ЗАПИСИ */
// view: active | archived | trash; onAction(note, 'pin'|'archive'|'unarchive'|'trash'|'restore'|'purge')
function NoteCard({ note, onOpen, onAction, view = "active", index = 0 }) {
  const body = note.summary || note.raw_text;
  const rem = note.reminder && note.reminder.status === "pending" ? formatRemind(note.reminder.remind_at) : null;
  const atts = note.attachments || [];
  const media = atts.filter((a) => a.type === "image" || a.type === "video");
  const links = atts.filter((a) => a.type === "link");

  // действия зависят от вида
  const actions = view === "trash"
    ? [{ k: "restore", icon: "refresh", label: "Вернуть", tone: "accent" }, { k: "purge", icon: "trash", label: "Навсегда", tone: "danger" }]
    : view === "archived"
    ? [{ k: "unarchive", icon: "inbox", label: "Из архива", tone: "accent" }, { k: "trash", icon: "trash", label: "В корзину", tone: "danger" }]
    : [{ k: "pin", icon: "star", label: note.pinned ? "Открепить" : "Закрепить", tone: "amber" }, { k: "archive", icon: "inbox", label: "В архив", tone: "muted" }, { k: "trash", icon: "trash", label: "Удалить", tone: "danger" }];

  const [dx, setDx] = React.useState(0);
  const [open, setOpen] = React.useState(false);
  const start = React.useRef(null);
  const moved = React.useRef(false);
  const revealW = actions.length * 60;

  const onStart = (e) => { start.current = e.touches[0].clientX; moved.current = false; };
  const onMove = (e) => {
    if (start.current == null) return;
    let d = e.touches[0].clientX - start.current + (open ? -revealW : 0);
    if (Math.abs(d - (open ? -revealW : 0)) > 6) moved.current = true;
    d = Math.max(-revealW - 20, Math.min(0, d));
    setDx(d);
  };
  const onEnd = () => {
    const shouldOpen = dx < -revealW / 2;
    setOpen(shouldOpen);
    setDx(shouldOpen ? -revealW : 0);
    start.current = null;
  };
  const handleClick = () => {
    if (moved.current) return;
    if (open) { setOpen(false); setDx(0); return; }
    onOpen(note);
  };
  const doAction = (k) => { setOpen(false); setDx(0); onAction && onAction(note, k); };

  return (
    <div className="relative">
      {/* задний слой действий (свайп) — виден только во время свайпа */}
      <div className="absolute inset-y-0 right-0 flex items-stretch rounded-2xl overflow-hidden" style={{ width: revealW, opacity: open || dx < 0 ? 1 : 0, pointerEvents: open || dx < 0 ? "auto" : "none" }}>
        {actions.map((a) => (
          <button key={a.k} onClick={() => doAction(a.k)}
            className={`flex-1 flex flex-col items-center justify-center gap-1 text-[11px] font-semibold swipe-act-${a.tone}`}>
            <Icon name={a.icon} size={18} strokeWidth={a.k === "pin" && note.pinned ? 2.4 : 1.8} />
            {a.label}
          </button>
        ))}
      </div>

      <article
        onClick={handleClick}
        onTouchStart={onStart} onTouchMove={onMove} onTouchEnd={onEnd}
        style={{ animationDelay: `${Math.min(index, 10) * 35}ms`, transform: `translateX(${dx}px)`, transition: start.current == null ? "transform .22s cubic-bezier(.2,.7,.3,1)" : "none" }}
        className="note-card group card-in relative cursor-pointer rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-4 sm:p-5 hover:border-[var(--border-strong)] hover:shadow-[0_8px_28px_-12px_var(--shadow)] transition-colors"
      >
        {/* действия на ховере (десктоп) */}
        <div className="hidden md:flex absolute top-3 right-3 items-center gap-1 opacity-0 group-hover:opacity-100 transition bg-[var(--surface)]/90 backdrop-blur rounded-lg p-0.5">
          {actions.map((a) => (
            <button key={a.k} onClick={(e) => { e.stopPropagation(); doAction(a.k); }} title={a.label}
              className={`grid place-items-center w-7 h-7 rounded-md hover:bg-[var(--surface-2)] act-hover-${a.tone}`}>
              <Icon name={a.icon} size={15} strokeWidth={a.k === "pin" && note.pinned ? 2.4 : 1.8} />
            </button>
          ))}
        </div>

        <div className="flex items-center gap-2 mb-2.5">
          {note.pinned && view === "active" && <Icon name="star" size={14} className="str-text-amber shrink-0" strokeWidth={2.4} />}
          <CategoryBadge category={note.category} />
          <div className="ml-auto flex items-center gap-2 text-[12px] text-[var(--muted)] shrink-0 md:group-hover:opacity-0 transition-opacity">
            <SourceMark source={note.source} />
            <span className="whitespace-nowrap">{formatCreated(note.created_at)}</span>
          </div>
        </div>

        <p className="text-[15px] sm:text-base leading-snug text-[var(--text)] font-medium" style={{ textWrap: "pretty" }}>
          {body}
        </p>
        {note.summary && (
          <p className="mt-1 text-[13px] leading-relaxed text-[var(--muted)] line-clamp-2">{note.raw_text}</p>
        )}

        {(note.tags.length > 0 || rem) && (
          <div className="mt-3 flex flex-wrap items-center gap-1.5">
            {rem && (
              <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold reminder-tone-${rem.tone} reminder-chip-${rem.tone}`}>
                <Icon name="bell" size={12} strokeWidth={1.9} />
                {rem.text}
              </span>
            )}
            {note.tags.map((t) => <TagPill key={t}>{t}</TagPill>)}
          </div>
        )}

        {atts.length > 0 && (
          <div className="mt-3 flex flex-wrap items-center gap-2">
            {media.slice(0, 3).map((a) => (
              <div key={a.id} className="att-thumb relative w-12 h-12 rounded-lg overflow-hidden grid place-items-center text-[var(--muted)]">
                {a.type === "image" && a.url ? (
                  <img src={a.url} alt={a.name || ""} className="w-full h-full object-cover" />
                ) : (
                  <Icon name={a.type === "video" ? "video" : "image"} size={20} />
                )}
                {a.type === "video" && <span className="absolute inset-0 grid place-items-center bg-black/25 text-white"><Icon name="video" size={18} /></span>}
              </div>
            ))}
            {links.slice(0, 2).map((a) => (
              <span key={a.id} className="inline-flex items-center gap-1.5 max-w-[180px] rounded-lg bg-[var(--surface-2)] border border-[var(--border)] px-2 py-1 text-[12px] text-[var(--muted)]">
                <Icon name="link" size={13} className="shrink-0" />
                <span className="truncate">{a.name || hostOf(a.url)}</span>
              </span>
            ))}
            {atts.length > media.slice(0,3).length + links.slice(0,2).length && (
              <span className="text-[12px] text-[var(--muted)] inline-flex items-center gap-1"><Icon name="paperclip" size={13} /> +{atts.length - media.slice(0,3).length - links.slice(0,2).length}</span>
            )}
          </div>
        )}
      </article>
    </div>
  );
}

function hostOf(url) {
  try { return new URL(url).hostname.replace(/^www\./, ""); } catch (e) { return url || "ссылка"; }
}

/* ===================================================== БАЗОВАЯ ОБЁРТКА МОДАЛКИ */
function Modal({ open, onClose, children, maxW = "max-w-lg" }) {
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [open]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4">
      <div className="absolute inset-0 modal-backdrop" onClick={onClose} />
      <div className={`relative w-full ${maxW} modal-panel bg-[var(--surface)] border border-[var(--border)] rounded-t-3xl sm:rounded-3xl shadow-2xl max-h-[92vh] overflow-y-auto`}>
        {children}
      </div>
    </div>
  );
}

/* ===================================================== ПОЛЕ-ВВОД ТЕГОВ */
function TagEditor({ tags, onChange }) {
  const [val, setVal] = React.useState("");
  const add = () => {
    const t = val.trim().replace(/^#/, "");
    if (t && !tags.includes(t)) onChange([...tags, t]);
    setVal("");
  };
  return (
    <div className="flex flex-wrap items-center gap-1.5 rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-2.5 py-2 min-h-[42px]">
      {tags.map((t) => <TagPill key={t} onRemove={() => onChange(tags.filter((x) => x !== t))}>{t}</TagPill>)}
      <input
        value={val}
        onChange={(e) => setVal(e.target.value)}
        onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); add(); } if (e.key === "Backspace" && !val && tags.length) onChange(tags.slice(0, -1)); }}
        onBlur={add}
        placeholder={tags.length ? "" : "добавить тег…"}
        className="flex-1 min-w-[80px] bg-transparent outline-none text-[13px] text-[var(--text)] placeholder:text-[var(--muted)]"
      />
    </div>
  );
}

/* ===================================================== РЕДАКТОР ВЛОЖЕНИЙ */
function AttachmentEditor({ attachments, onChange }) {
  const [linkUrl, setLinkUrl] = React.useState("");
  const [adding, setAdding] = React.useState(false);
  const fileRef = React.useRef(null);

  const addLink = () => {
    let u = linkUrl.trim();
    if (!u) return;
    if (!/^https?:\/\//i.test(u)) u = "https://" + u;
    onChange([...attachments, { id: api._uuid(), type: "link", url: u, name: hostOf(u) }]);
    setLinkUrl(""); setAdding(false);
  };

  const [uploading, setUploading] = React.useState(false);
  const onFiles = async (e) => {
    const files = [...(e.target.files || [])];
    e.target.value = "";
    if (!files.length) return;
    setUploading(true);
    const next = [];
    for (const f of files) {
      try {
        const up = await api.uploadMedia(f); // → постоянный /media/... URL
        next.push({ id: api._uuid(), type: up.type, url: up.url, name: up.name || f.name, size: up.size });
      } catch (err) {
        next.push({ id: api._uuid(), type: f.type.startsWith("video") ? "video" : "image", url: URL.createObjectURL(f), name: f.name });
      }
    }
    setUploading(false);
    onChange([...attachments, ...next]);
  };

  const remove = (id) => onChange(attachments.filter((a) => a.id !== id));

  return (
    <div className="space-y-2.5">
      {attachments.length > 0 && (
        <div className="flex flex-wrap gap-2">
          {attachments.map((a) => (
            <div key={a.id} className="relative group/att">
              {a.type === "image" || a.type === "video" ? (
                <div className="att-thumb w-16 h-16 rounded-xl overflow-hidden grid place-items-center text-[var(--muted)]">
                  {a.type === "image" && a.url ? <img src={a.url} alt={a.name || ""} className="w-full h-full object-cover" /> : <Icon name={a.type === "video" ? "video" : "image"} size={22} />}
                </div>
              ) : (
                <a href={a.url} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()} className="flex items-center gap-1.5 max-w-[200px] h-16 px-3 rounded-xl att-thumb text-[13px] text-[var(--text)] hover:border-[var(--accent)] transition">
                  <Icon name="link" size={15} className="shrink-0 text-[var(--accent)]" />
                  <span className="truncate">{a.name || hostOf(a.url)}</span>
                </a>
              )}
              <button onClick={() => remove(a.id)} className="absolute -top-1.5 -right-1.5 grid place-items-center w-5 h-5 rounded-full bg-[var(--danger)] text-white shadow opacity-0 group-hover/att:opacity-100 transition" aria-label="Удалить вложение">
                <Icon name="close" size={11} strokeWidth={2.6} />
              </button>
            </div>
          ))}
        </div>
      )}

      {adding ? (
        <div className="flex gap-1.5">
          <input
            autoFocus
            value={linkUrl}
            onChange={(e) => setLinkUrl(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addLink(); } if (e.key === "Escape") setAdding(false); }}
            placeholder="https://…"
            className="flex-1 rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-3 h-9 text-[13px] text-[var(--text)] outline-none focus:border-[var(--accent)] transition"
          />
          <Button size="sm" variant="soft" onClick={addLink}>Добавить</Button>
          <Button size="sm" variant="ghost" onClick={() => setAdding(false)}>Отмена</Button>
        </div>
      ) : (
        <div className="flex flex-wrap gap-2">
          <button onClick={() => fileRef.current && fileRef.current.click()} disabled={uploading} className="inline-flex items-center gap-1.5 h-9 px-3 rounded-xl border border-[var(--border)] text-[13px] font-semibold text-[var(--muted)] hover:text-[var(--text)] hover:border-[var(--border-strong)] transition disabled:opacity-50">
            {uploading ? <Spinner size={14} /> : <Icon name="image" size={15} />} Фото / видео
          </button>
          <button onClick={() => setAdding(true)} className="inline-flex items-center gap-1.5 h-9 px-3 rounded-xl border border-[var(--border)] text-[13px] font-semibold text-[var(--muted)] hover:text-[var(--text)] hover:border-[var(--border-strong)] transition">
            <Icon name="link" size={15} /> Ссылка
          </button>
          <input ref={fileRef} type="file" accept="image/*,video/*" multiple className="hidden" onChange={onFiles} />
        </div>
      )}
    </div>
  );
}

/* ===================================================== МОДАЛКА ЗАПИСИ (4.1) */
function NoteModal({ note, open, onClose, onSaved, onDeleted, onOpenRelated }) {
  const cats = Store.useCategories();
  const [text, setText] = React.useState("");
  const [category, setCategory] = React.useState("other");
  const [tags, setTags] = React.useState([]);
  const [attachments, setAttachments] = React.useState([]);
  const [remindAt, setRemindAt] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [confirmDel, setConfirmDel] = React.useState(false);
  const [related, setRelated] = React.useState([]);

  React.useEffect(() => {
    if (note) {
      setText(note.raw_text);
      setCategory(note.category);
      setTags(note.tags || []);
      setAttachments(note.attachments || []);
      setRemindAt(note.reminder ? toLocalInput(note.reminder.remind_at) : "");
      setConfirmDel(false);
      setRelated([]);
      api.getRelated(note.id).then(setRelated);
    }
  }, [note]);

  if (!note) return null;

  const save = async () => {
    setBusy(true);
    const updated = await api.updateNote(note.id, { raw_text: text, category, tags, attachments });
    // напоминание
    const hadRem = !!note.reminder;
    const wantRem = !!remindAt;
    if (wantRem && !hadRem) {
      await api.createReminder({ note_id: note.id, remind_at: fromLocalInput(remindAt), text: (updated.summary || text).slice(0, 40) });
    } else if (wantRem && hadRem) {
      await api.updateReminder(note.reminder.id, { remind_at: fromLocalInput(remindAt) });
    } else if (!wantRem && hadRem) {
      await api.updateReminder(note.reminder.id, { status: "cancelled" });
    }
    setBusy(false);
    onSaved && onSaved();
    onClose();
  };

  const del = async () => {
    setBusy(true);
    await api.deleteNote(note.id);
    setBusy(false);
    onDeleted && onDeleted();
    onClose();
  };

  const togglePin = async () => { await api.pinNote(note.id, !note.pinned); note.pinned = !note.pinned; onSaved && onSaved(); setRelated((r) => [...r]); };
  const archive = async () => { await api.archiveNote(note.id, true); onSaved && onSaved(); onClose(); };

  // быстрые действия по напоминанию-задаче
  const quickReminder = async (action) => {
    if (!note.reminder) return;
    setBusy(true);
    if (action === "done") await api.updateReminder(note.reminder.id, { status: "done" });
    else if (action === "cancel") await api.updateReminder(note.reminder.id, { status: "cancelled" });
    else {
      const base = new Date(note.reminder.remind_at);
      const now = new Date();
      const from = base > now ? base : now;
      from.setDate(from.getDate() + action);
      await api.updateReminder(note.reminder.id, { remind_at: from.toISOString(), status: "pending" });
    }
    setBusy(false);
    onSaved && onSaved();
    onClose();
  };

  // экспорт записи как события календаря (.ics)
  const toCalendar = () => {
    const dt = remindAt ? new Date(fromLocalInput(remindAt)) : new Date(Date.now() + 86400e3);
    const pad = (n) => String(n).padStart(2, "0");
    const fmt = (d) => d.getUTCFullYear() + pad(d.getUTCMonth() + 1) + pad(d.getUTCDate()) + "T" + pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + "00Z";
    const end = new Date(dt.getTime() + 3600e3);
    const title = (note.summary || text).slice(0, 60).replace(/\n/g, " ");
    const ics = [
      "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Umnyy Bloknot//RU", "BEGIN:VEVENT",
      "UID:" + note.id + "@bloknot", "DTSTAMP:" + fmt(new Date()),
      "DTSTART:" + fmt(dt), "DTEND:" + fmt(end),
      "SUMMARY:" + title, "DESCRIPTION:" + text.replace(/\n/g, "\\n"),
      "END:VEVENT", "END:VCALENDAR",
    ].join("\r\n");
    const blob = new Blob([ics], { type: "text/calendar" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = "event.ics";
    a.click();
    setTimeout(() => URL.revokeObjectURL(a.href), 1000);
  };

  return (
    <Modal open={open} onClose={onClose}>
      <div className="sticky top-0 z-10 flex items-center gap-3 px-5 sm:px-6 pt-5 pb-3 bg-[var(--surface)]/95 backdrop-blur border-b border-[var(--border)]">
        <CategoryBadge category={category} />
        <span className="text-[12px] text-[var(--muted)] inline-flex items-center gap-1.5">
          <SourceMark source={note.source} /> {formatCreated(note.created_at)}
        </span>
        <div className="ml-auto flex items-center gap-0.5">
          <button onClick={togglePin} className={`grid place-items-center w-8 h-8 rounded-lg transition ${note.pinned ? "str-text-amber" : "text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--surface-2)]"}`} title={note.pinned ? "Открепить" : "Закрепить"}>
            <Icon name="star" size={17} strokeWidth={note.pinned ? 2.4 : 1.7} />
          </button>
          <button onClick={archive} className="grid place-items-center w-8 h-8 rounded-lg text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--surface-2)] transition" title="В архив">
            <Icon name="inbox" size={17} />
          </button>
          <button onClick={onClose} className="grid place-items-center w-8 h-8 rounded-lg text-[var(--muted)] hover:bg-[var(--surface-2)] transition" aria-label="Закрыть">
            <Icon name="close" size={18} />
          </button>
        </div>
      </div>

      <div className="px-5 sm:px-6 py-4 space-y-4">
        <Field label="Текст записи">
          <textarea
            value={text}
            onChange={(e) => setText(e.target.value)}
            rows={5}
            className="w-full resize-y rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-3.5 py-3 text-[15px] leading-relaxed text-[var(--text)] outline-none focus:border-[var(--accent)] transition"
          />
        </Field>

        <div className="grid sm:grid-cols-2 gap-4">
          <Field label="Категория">
            <div className="relative">
              <select
                value={category}
                onChange={(e) => setCategory(e.target.value)}
                className="w-full appearance-none rounded-xl border border-[var(--border)] bg-[var(--surface-2)] pl-3.5 pr-9 h-[42px] text-sm text-[var(--text)] outline-none focus:border-[var(--accent)] transition cursor-pointer"
              >
                {cats.map((c) => <option key={c.key} value={c.key}>{c.label}</option>)}
              </select>
              <Icon name="chevron" size={16} className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 rotate-90 text-[var(--muted)]" />
            </div>
          </Field>
          <Field label="Напоминание">
            <div className="flex gap-1.5">
              <input
                type="datetime-local"
                value={remindAt}
                onChange={(e) => setRemindAt(e.target.value)}
                className="flex-1 rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-3 h-[42px] text-[13px] text-[var(--text)] outline-none focus:border-[var(--accent)] transition"
              />
              {remindAt && (
                <button onClick={() => setRemindAt("")} className="shrink-0 px-2.5 rounded-xl border border-[var(--border)] text-[var(--muted)] hover:text-[var(--danger)] hover:border-[var(--danger)] transition" title="Убрать напоминание">
                  <Icon name="close" size={15} />
                </button>
              )}
            </div>
          </Field>
        </div>

        {note.reminder && note.reminder.status === "pending" && (
          <div>
            <span className="mb-1.5 block text-[12px] font-semibold uppercase tracking-wide text-[var(--muted)]">Статус задачи</span>
            <div className="flex flex-wrap gap-1.5">
              <button onClick={() => quickReminder("done")} disabled={busy} className="inline-flex items-center gap-1 text-[13px] font-semibold str-text-green px-3 py-2 rounded-xl border border-[var(--border)] hover:bg-[var(--surface-2)] transition">
                <Icon name="check" size={15} /> Готово
              </button>
              <button onClick={() => quickReminder(1)} disabled={busy} className="inline-flex items-center gap-1 text-[13px] font-semibold text-[var(--accent)] px-3 py-2 rounded-xl border border-[var(--border)] hover:bg-[var(--surface-2)] transition">
                <Icon name="bell" size={14} /> +1 день
              </button>
              <button onClick={() => quickReminder(7)} disabled={busy} className="inline-flex items-center gap-1 text-[13px] font-semibold text-[var(--accent)] px-3 py-2 rounded-xl border border-[var(--border)] hover:bg-[var(--surface-2)] transition">
                +7 дней
              </button>
              <button onClick={() => quickReminder("cancel")} disabled={busy} className="inline-flex items-center gap-1 text-[13px] font-semibold text-[var(--muted)] hover:text-[var(--danger)] px-3 py-2 rounded-xl border border-[var(--border)] hover:bg-[var(--surface-2)] transition">
                Отменить
              </button>
            </div>
          </div>
        )}

        <Field label="Теги">
          <TagEditor tags={tags} onChange={setTags} />
        </Field>

        <Field label="Вложения">
          <AttachmentEditor attachments={attachments} onChange={setAttachments} />
        </Field>

        {/* быстрые действия ИИ */}
        <div className="flex flex-wrap gap-2 pt-1">
          <button onClick={toCalendar} className="inline-flex items-center gap-1.5 h-9 px-3 rounded-xl border border-[var(--border)] text-[13px] font-semibold text-[var(--muted)] hover:text-[var(--accent)] hover:border-[var(--accent)] transition">
            <Icon name="calendar" size={15} /> В календарь (.ics)
          </button>
          {!remindAt && (
            <button onClick={() => setRemindAt(toLocalInput(new Date(Date.now() + 86400e3).setHours(10, 0, 0, 0)))} className="inline-flex items-center gap-1.5 h-9 px-3 rounded-xl border border-[var(--border)] text-[13px] font-semibold text-[var(--muted)] hover:text-[var(--accent)] hover:border-[var(--accent)] transition">
              <Icon name="bell" size={15} /> Напомнить завтра
            </button>
          )}
        </div>

        {/* похожие записи */}
        {related.length > 0 && (
          <div>
            <div className="flex items-center gap-1.5 mb-2 text-[var(--muted)]">
              <Icon name="sparkles" size={14} className="text-[var(--accent)]" />
              <span className="text-[12px] font-bold uppercase tracking-wide">Похожие записи</span>
            </div>
            <div className="grid gap-1.5">
              {related.map((r) => (
                <button key={r.id} onClick={() => onOpenRelated && onOpenRelated(r)} className="text-left flex items-center gap-2.5 rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-3 py-2 hover:border-[var(--border-strong)] transition">
                  <span className={`w-2 h-2 rounded-full shrink-0 dot-${catFrom(r.category).color}`} />
                  <span className="text-[13px] text-[var(--text)] truncate flex-1">{r.summary || r.raw_text}</span>
                  <Icon name="chevron" size={14} className="text-[var(--muted)] shrink-0" />
                </button>
              ))}
            </div>
          </div>
        )}
      </div>

      <div className="sticky bottom-0 flex items-center gap-2 px-5 sm:px-6 py-3.5 bg-[var(--surface)]/95 backdrop-blur border-t border-[var(--border)]">
        {!confirmDel ? (
          <Button variant="danger" size="md" onClick={() => setConfirmDel(true)} disabled={busy}>
            <Icon name="trash" size={16} /> Удалить
          </Button>
        ) : (
          <div className="flex items-center gap-2">
            <span className="text-[13px] text-[var(--muted)] hidden sm:inline">Точно?</span>
            <Button variant="danger" size="md" onClick={del} disabled={busy}>Да, удалить</Button>
            <Button variant="ghost" size="md" onClick={() => setConfirmDel(false)}>Отмена</Button>
          </div>
        )}
        <div className="ml-auto flex items-center gap-2">
          <Button variant="ghost" size="md" onClick={onClose} disabled={busy}>Закрыть</Button>
          <Button variant="primary" size="md" onClick={save} disabled={busy}>
            {busy ? <Spinner /> : <Icon name="check" size={16} />} Сохранить
          </Button>
        </div>
      </div>
    </Modal>
  );
}

/* ===================================================== МОДАЛКА СОЗДАНИЯ (4.2) */
function CreateModal({ open, onClose, onCreated }) {
  const [text, setText] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [listening, setListening] = React.useState(false);
  const ref = React.useRef(null);
  const recog = React.useRef(null);

  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  const voiceSupported = !!SR;

  const toggleVoice = () => {
    if (!SR) return;
    if (listening) { recog.current && recog.current.stop(); return; }
    const r = new SR();
    r.lang = "ru-RU"; r.continuous = true; r.interimResults = false;
    r.onresult = (e) => {
      let add = "";
      for (let i = e.resultIndex; i < e.results.length; i++) add += e.results[i][0].transcript;
      setText((t) => (t ? t + " " : "") + add.trim());
    };
    r.onend = () => setListening(false);
    r.onerror = () => setListening(false);
    recog.current = r; r.start(); setListening(true);
  };

  React.useEffect(() => {
    if (open) { setText(""); setTimeout(() => ref.current && ref.current.focus(), 60); }
    return () => { if (recog.current) try { recog.current.stop(); } catch (e) {} };
  }, [open]);

  const create = async () => {
    if (!text.trim()) return;
    setBusy(true);
    const note = await api.createNote({ raw_text: text.trim() });
    setBusy(false);
    onCreated && onCreated(note);
    onClose();
  };

  return (
    <Modal open={open} onClose={onClose}>
      <div className="flex items-center gap-2.5 px-5 sm:px-6 pt-5 pb-3 border-b border-[var(--border)]">
        <span className="grid place-items-center w-8 h-8 rounded-lg bg-[var(--accent-weak)] text-[var(--accent)]"><Icon name="plus" size={18} /></span>
        <h2 className="text-base font-bold text-[var(--text)]">Новая запись</h2>
        <button onClick={onClose} className="ml-auto p-1.5 rounded-lg text-[var(--muted)] hover:bg-[var(--surface-2)] transition" aria-label="Закрыть">
          <Icon name="close" size={18} />
        </button>
      </div>
      <div className="px-5 sm:px-6 py-4">
        <div className="relative">
          <textarea
            ref={ref}
            value={text}
            onChange={(e) => setText(e.target.value)}
            onKeyDown={(e) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") create(); }}
            rows={5}
            placeholder="О чём запись? Просто напиши мысль, задачу или идею…"
            className="w-full resize-y rounded-xl border border-[var(--border)] bg-[var(--surface-2)] px-4 py-3 pr-12 text-[15px] leading-relaxed text-[var(--text)] outline-none focus:border-[var(--accent)] transition placeholder:text-[var(--muted)]"
          />
          {voiceSupported && (
            <button onClick={toggleVoice} title={listening ? "Остановить диктовку" : "Голосовой ввод"}
              className={`absolute top-2.5 right-2.5 grid place-items-center w-9 h-9 rounded-lg transition ${listening ? "bg-[var(--danger)] text-white mic-pulse" : "bg-[var(--surface)] border border-[var(--border)] text-[var(--muted)] hover:text-[var(--accent)]"}`}>
              <Icon name="mic" size={18} />
            </button>
          )}
        </div>
        <p className="mt-2.5 flex items-center gap-1.5 text-[12px] text-[var(--muted)]">
          <Icon name="sparkles" size={14} className="text-[var(--accent)]" />
          {listening ? "Слушаю… говори — текст появится сам" : "Категорию, теги и напоминание ИИ проставит автоматически"}
        </p>
      </div>
      <div className="flex items-center gap-2 px-5 sm:px-6 py-3.5 border-t border-[var(--border)]">
        <span className="text-[12px] text-[var(--muted)] hidden sm:inline">⌘↵ — сохранить</span>
        <div className="ml-auto flex items-center gap-2">
          <Button variant="ghost" onClick={onClose} disabled={busy}>Отмена</Button>
          <Button variant="primary" onClick={create} disabled={busy || !text.trim()}>
            {busy ? <Spinner /> : <Icon name="check" size={16} />} Сохранить
          </Button>
        </div>
      </div>
    </Modal>
  );
}

function Field({ label, children }) {
  return (
    <label className="block">
      <span className="mb-1.5 block text-[12px] font-semibold uppercase tracking-wide text-[var(--muted)]">{label}</span>
      {children}
    </label>
  );
}

Object.assign(window, { NoteCard, Modal, NoteModal, CreateModal, TagEditor, AttachmentEditor, Field, hostOf });
