// Passage — UI components.
const { useState: uS, useRef: uR, useEffect: uE, useMemo: uM } = React;

function MobileHeader({ view, onBack, title, right, leftOverride }) {
  if (view === "viewer") return null;
  return (
    <header className="m-hd">
      {leftOverride !== undefined ? leftOverride : (
        view === "album" ? (
          <button className="m-hd-back" onClick={onBack} aria-label="Back">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
              <path d="M15 19l-7-7 7-7" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </button>
        ) : <div className="m-hd-logo">◐</div>
      )}
      <div className="m-hd-title">{title}</div>
      <div className="m-hd-right">{right}</div>
    </header>
  );
}

function ProfileStrip({ profileUser, albums, canEdit, onEdit, onLogout }) {
  const totalPhotos = albums.reduce((n, a) => n + a.photos.length, 0);
  const avatar = albums.find((a) => a.coverUrl)?.coverUrl;
  return (
    <section className="m-profile">
      <div className="m-pf-row">
        <div className="m-pf-avatar" style={avatar ? { backgroundImage: `url(${avatar})` } : null}>
          {!avatar && <span className="m-pf-avatar-ph">◐</span>}
        </div>
        <div className="m-pf-stats">
          <div className="m-pf-stat"><b>{albums.length}</b><span>albums</span></div>
          <div className="m-pf-stat"><b>{totalPhotos}</b><span>photos</span></div>
        </div>
      </div>
      <div className="m-pf-name">{profileUser.display || profileUser.username}</div>
      <div className="m-pf-handle">@{profileUser.username}</div>
      {profileUser.bio && <div className="m-pf-bio">{profileUser.bio}</div>}
      {canEdit && (
        <div className="m-pf-actions">
          <button className="m-btn-ghost m-btn-xs" onClick={onEdit}>Edit profile</button>
          <button className="m-btn-ghost m-btn-xs" onClick={onLogout}>Log out</button>
        </div>
      )}
    </section>
  );
}

function TabStrip({ tab, setTab, tabs }) {
  return (
    <nav className="m-tabs">
      {tabs.map((t) => (
        <button
          key={t.id}
          className={"m-tab " + (tab === t.id ? "is-active" : "")}
          onClick={() => setTab(t.id)}
        >{t.label}</button>
      ))}
    </nav>
  );
}

function AlbumsList({ albums, onOpen, onNew, canEdit }) {
  if (albums.length === 0) {
    return (
      <div className="m-empty">
        <div className="m-empty-mark">◐</div>
        <div className="m-empty-t">No albums yet</div>
        <div className="m-empty-s">{canEdit
          ? "Create your first album to start collecting photos from your trips."
          : "This gallery is still empty."}</div>
        {canEdit && <button className="m-btn-primary" onClick={onNew}>+ New album</button>}
      </div>
    );
  }
  return (
    <div className="m-albums">
      {albums.map((a, i) => (
        <figure key={a.id} className="m-alb-card" onClick={() => onOpen(a.id)}>
          <div className="m-alb-cover" style={a.coverUrl ? { backgroundImage: `url(${a.coverUrl})` } : null}>
            {!a.coverUrl && <span className="m-alb-cover-ph">◐</span>}
          </div>
          <figcaption className="m-alb-cap">
            <div className="m-alb-t">
              <span className="m-alb-num">{String(i + 1).padStart(2, "0")}</span>
              <span className="m-alb-title">{a.title}</span>
            </div>
            <div className="m-alb-meta" dir="ltr">
              {(() => {
                const when = [a.month, a.year].filter(Boolean).join(" ");
                const n = a.photos.length;
                const parts = [];
                if (a.country) parts.push(<bdi key="c">{a.country}</bdi>);
                if (when)      parts.push(<bdi key="w">{when}</bdi>);
                parts.push(<bdi key="n">{n} {n === 1 ? "photo" : "photos"}</bdi>);
                return parts.flatMap((p, i) => i === 0 ? [p] : [" · ", p]);
              })()}
            </div>
          </figcaption>
        </figure>
      ))}
    </div>
  );
}

function AllPhotosGrid({ albums, onOpenPhoto, onNew, canEdit }) {
  const all = [];
  albums.forEach((a) => a.photos.forEach((p, idx) => all.push({ albumId: a.id, photoIdx: idx, src: p.src, full: p.full, title: a.title })));
  if (all.length === 0) {
    return (
      <div className="m-empty">
        <div className="m-empty-mark">◐</div>
        <div className="m-empty-t">No photos yet</div>
        <div className="m-empty-s">{canEdit ? "Photos you add to any album show up here." : "Nothing here yet."}</div>
        {canEdit && <button className="m-btn-primary" onClick={onNew}>+ New album</button>}
      </div>
    );
  }
  return (
    <div className="m-grid">
      {all.map((p, i) => (
        <button
          key={i}
          className="m-grid-cell"
          onClick={() => onOpenPhoto(p.albumId, p.photoIdx)}
          aria-label={p.title}
        >
          <img
            src={p.src}
            alt=""
            loading={i < 9 ? "eager" : "lazy"}
            decoding="async"
            fetchpriority={i < 6 ? "high" : "auto"}
            onError={(e) => { if (p.full && e.target.src !== p.full) e.target.src = p.full; }}
          />
        </button>
      ))}
    </div>
  );
}

function AlbumView({ album, canEdit, onOpenPhoto, onAddPhotos, onDeleteAlbum, onEditAlbum, uploading }) {
  const inputRef = uR(null);
  const pick = () => inputRef.current && inputRef.current.click();
  const onFiles = (e) => {
    const files = Array.from(e.target.files || []);
    if (files.length) onAddPhotos(files);
    e.target.value = "";
  };
  return (
    <div className="m-album">
      <div className="m-al-hero" style={album.coverUrl ? { backgroundImage: `url(${album.coverUrl})` } : null}>
        {!album.coverUrl && <div className="m-al-hero-ph"><span>◐</span></div>}
        <div className="m-al-veil" />
        <div className="m-al-meta">
          <div className="m-al-sub" dir="ltr">
            {(() => {
              const when = [album.month, album.year].filter(Boolean).join(" ");
              if (!album.country && !when) return "New album";
              const parts = [];
              if (album.country) parts.push(<bdi key="c">{album.country}</bdi>);
              if (when)          parts.push(<bdi key="w">{when}</bdi>);
              return parts.flatMap((p, i) => i === 0 ? [p] : [" · ", p]);
            })()}
          </div>
          <h1 className="m-al-title">{album.title}</h1>
          {album.subtitle && <div className="m-al-italic">{album.subtitle}</div>}
        </div>
      </div>
      {album.caption && <p className="m-al-caption">{album.caption}</p>}
      <div className="m-al-stats">
        <div><b>{album.photos.length}</b><span>photos</span></div>
        <div><b>{album.year || "—"}</b><span>year</span></div>
        <div><b>{album.country || "—"}</b><span>area</span></div>
      </div>

      {canEdit && (
        <div className="m-al-actions">
          <button className="m-btn-ghost" onClick={pick} disabled={!!uploading}>
            {uploading ? `Uploading ${uploading.done} / ${uploading.total}…` : (
              <>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
                  <path d="M12 5v14M5 12h14" strokeLinecap="round" />
                </svg>
                Add photos
              </>
            )}
          </button>
          <button className="m-btn-ghost" onClick={onEditAlbum}>Edit</button>
          <button className="m-btn-ghost m-danger" onClick={onDeleteAlbum}>Delete</button>
          <input
            ref={inputRef}
            type="file"
            accept="image/*"
            multiple
            style={{ display: "none" }}
            onChange={onFiles}
          />
        </div>
      )}

      {album.photos.length === 0 ? (
        <div className="m-empty m-empty-inline">
          <div className="m-empty-s">{canEdit ? 'Tap "Add photos" to pick from your Photos library.' : "No photos in this album yet."}</div>
        </div>
      ) : (
        <div className="m-grid m-al-grid">
          {album.photos.map((p, i) => (
            <button
              key={p.id}
              className="m-grid-cell"
              onClick={() => onOpenPhoto(album.id, i)}
              aria-label={p.caption || "Photo"}
            >
              <img
                src={p.src}
                alt=""
                loading={i < 9 ? "eager" : "lazy"}
                decoding="async"
                fetchpriority={i < 6 ? "high" : "auto"}
                onError={(e) => { if (p.full && e.target.src !== p.full) e.target.src = p.full; }}
              />
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function PhotoViewer({ album, idx, canEdit, onClose, onChange, onDelete, onSetCover }) {
  const stageRef = uR(null);
  const startX = uR(0);
  const startY = uR(0);
  const lastX = uR(0);
  const lastT = uR(0);
  const velocity = uR(0);
  const axisLocked = uR(null); // 'x' | 'y' | null
  const [stageW, setStageW] = uS(0);
  const [offset, setOffset] = uS(0);
  const [dragging, setDragging] = uS(false);
  const [settling, setSettling] = uS(null); // null | +1 (next) | -1 (prev)

  uE(() => {
    if (!stageRef.current) return;
    const el = stageRef.current;
    const update = () => setStageW(el.clientWidth);
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  uE(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowLeft" && idx > 0) onChange(idx - 1);
      else if (e.key === "ArrowRight" && idx < album.photos.length - 1) onChange(idx + 1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [idx, album, onChange, onClose]);

  // Warm adjacent photos so swipes feel instant.
  uE(() => {
    if (!album) return;
    for (const d of [-1, 1]) {
      const n = album.photos[idx + d];
      if (n) { const im = new Image(); im.src = n.display || n.full || n.src; }
    }
  }, [album, idx]);

  if (idx == null || !album) return null;
  const photo = album.photos[idx];
  if (!photo) return null;
  const prev = album.photos[idx - 1];
  const next = album.photos[idx + 1];

  const onStart = (e) => {
    if (settling) return;
    const t = e.touches ? e.touches[0] : e;
    startX.current = t.clientX;
    startY.current = t.clientY;
    lastX.current = t.clientX;
    lastT.current = performance.now();
    velocity.current = 0;
    axisLocked.current = null;
    setDragging(true);
  };
  const onMove = (e) => {
    if (!dragging) return;
    const t = e.touches ? e.touches[0] : e;
    const dx = t.clientX - startX.current;
    const dy = t.clientY - startY.current;
    // Lock axis once intent is clear (helps vertical scrolling on edge cases).
    if (axisLocked.current == null && (Math.abs(dx) > 8 || Math.abs(dy) > 8)) {
      axisLocked.current = Math.abs(dx) > Math.abs(dy) ? "x" : "y";
    }
    if (axisLocked.current === "y") return;
    // Track velocity for fling detection.
    const now = performance.now();
    const dt = Math.max(1, now - lastT.current);
    velocity.current = (t.clientX - lastX.current) / dt; // px/ms
    lastX.current = t.clientX;
    lastT.current = now;
    // Rubber band at boundaries.
    let capped = dx;
    if (dx > 0 && !prev) capped = dx * 0.25;
    if (dx < 0 && !next) capped = dx * 0.25;
    setOffset(capped);
  };
  const onEnd = () => {
    if (!dragging) return;
    setDragging(false);
    const threshold = Math.max(60, stageW * 0.18);
    const fling = Math.abs(velocity.current) > 0.6 && Math.abs(offset) > 24;
    const goNext = (offset < -threshold || (fling && velocity.current < 0)) && next;
    const goPrev = (offset >  threshold || (fling && velocity.current > 0)) && prev;
    if (goNext) { setSettling(1); setOffset(0); }
    else if (goPrev) { setSettling(-1); setOffset(0); }
    else setOffset(0);
  };

  const onTransitionEnd = (e) => {
    if (e.propertyName !== "transform") return;
    if (settling != null) {
      const dir = settling;
      setSettling(null);
      onChange(idx + dir);
    }
  };

  // Strip transform: base is -stageW (centers the current slot). Settling
  // animates to neighbour edge; drag adds px offset. Using percent for
  // layout so mount-time stageW=0 still positions the strip correctly.
  let tx;
  if (settling === 1)      tx = "-66.6667%";
  else if (settling === -1) tx = "0%";
  else                      tx = `calc(-33.3333% + ${offset}px)`;

  return (
    <div className="m-viewer">
      <div className="m-vw-top">
        <button className="m-vw-x" onClick={onClose} aria-label="Close">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
            <path d="M6 6l12 12M18 6L6 18" strokeLinecap="round" />
          </svg>
        </button>
        <div className="m-vw-count">
          {String(idx + 1).padStart(2, "0")} / {String(album.photos.length).padStart(2, "0")}
          {album.coverPhotoId === photo.id && <span className="m-vw-cover-tag">· cover</span>}
        </div>
        {canEdit ? (
          <div className="m-vw-actions">
            <button
              className={"m-vw-x " + (album.coverPhotoId === photo.id ? "is-on" : "")}
              onClick={() => onSetCover && onSetCover(idx)}
              aria-label="Set as cover"
              title={album.coverPhotoId === photo.id ? "This is the cover" : "Set as cover"}
              disabled={album.coverPhotoId === photo.id}
            >
              <svg width="20" height="20" viewBox="0 0 24 24" fill={album.coverPhotoId === photo.id ? "currentColor" : "none"} stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round">
                <path d="M12 3l2.6 5.3 5.9.9-4.3 4.2 1 5.9-5.2-2.8-5.2 2.8 1-5.9L3.5 9.2l5.9-.9L12 3z" />
              </svg>
            </button>
            <button className="m-vw-x" onClick={() => onDelete(idx)} aria-label="Delete photo">
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
                <path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2m-8 0l1 12a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2l1-12" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>
          </div>
        ) : <div style={{ width: 36 }} />}
      </div>
      <div
        className="m-vw-stage"
        ref={stageRef}
        onTouchStart={onStart} onTouchMove={onMove} onTouchEnd={onEnd} onTouchCancel={onEnd}
        onMouseDown={onStart} onMouseMove={onMove} onMouseUp={onEnd} onMouseLeave={onEnd}
      >
        <div
          key={idx}
          className="m-vw-strip"
          style={{
            transform: `translate3d(${tx}, 0, 0)`,
            transition: dragging ? "none" : "transform 0.32s cubic-bezier(0.22, 1, 0.36, 1)",
          }}
          onTransitionEnd={onTransitionEnd}
        >
          <div className="m-vw-slot">
            {prev && <img src={prev.display || prev.full || prev.src} alt="" draggable={false}
              onError={(e) => { if (prev.full && e.target.src !== prev.full) e.target.src = prev.full; }} />}
          </div>
          <div className="m-vw-slot">
            <img src={photo.display || photo.full || photo.src} alt={photo.caption || ""} draggable={false}
              onError={(e) => { if (photo.full && e.target.src !== photo.full) e.target.src = photo.full; }} />
          </div>
          <div className="m-vw-slot">
            {next && <img src={next.display || next.full || next.src} alt="" draggable={false}
              onError={(e) => { if (next.full && e.target.src !== next.full) e.target.src = next.full; }} />}
          </div>
        </div>
      </div>
      <div className="m-vw-cap">
        <div className="m-vw-t">{album.title}</div>
        {photo.caption && <div className="m-vw-s">{photo.caption}</div>}
      </div>
      <div className="m-vw-dots">
        {album.photos.map((_, i) => (
          <span key={i} className={"m-vw-dot " + (i === idx ? "is-on" : "")} />
        ))}
      </div>
    </div>
  );
}

function AlbumSheet({ open, initial, onClose, onSave, busy }) {
  const [title, setTitle] = uS("");
  const [subtitle, setSubtitle] = uS("");
  const [country, setCountry] = uS("");
  const [month, setMonth] = uS("");
  const [year, setYear] = uS("");
  const [caption, setCaption] = uS("");
  const [err, setErr] = uS("");

  uE(() => {
    if (!open) return;
    setTitle(initial?.title || "");
    setSubtitle(initial?.subtitle || "");
    setCountry(initial?.country || "");
    setMonth(initial?.month || "");
    setYear(initial?.year ? String(initial.year) : String(new Date().getFullYear()));
    setCaption(initial?.caption || "");
    setErr("");
  }, [open, initial]);

  if (!open) return null;

  const submit = async (e) => {
    e && e.preventDefault && e.preventDefault();
    if (!title.trim()) return;
    setErr("");
    try {
      await onSave({
        title: title.trim(),
        subtitle: subtitle.trim(),
        country: country.trim(),
        month: month.trim(),
        year: year ? Number(year) : null,
        caption: caption.trim(),
      });
    } catch (e) { setErr(e.message || "Error"); }
  };

  return (
    <div className="m-sheet-wrap" onClick={onClose}>
      <form className="m-sheet" onClick={(e) => e.stopPropagation()} onSubmit={submit}>
        <div className="m-sheet-hd">
          <button type="button" className="m-sheet-x" onClick={onClose}>Cancel</button>
          <div className="m-sheet-t">{initial ? "Edit album" : "New album"}</div>
          <button type="submit" className="m-sheet-ok" disabled={!title.trim() || busy}>
            {busy ? "…" : (initial ? "Save" : "Create")}
          </button>
        </div>
        <div className="m-sheet-body">
          {err && <div className="m-err">{err}</div>}
          <label className="m-field">
            <span>Title</span>
            <input autoFocus value={title} onChange={(e) => setTitle(e.target.value)} placeholder="e.g. Kyoto" />
          </label>
          <label className="m-field">
            <span>Subtitle</span>
            <input value={subtitle} onChange={(e) => setSubtitle(e.target.value)} placeholder="optional tagline" />
          </label>
          <div className="m-field-row">
            <label className="m-field"><span>Area</span>
              <input value={country} onChange={(e) => setCountry(e.target.value)} placeholder="e.g. Judean Desert" />
            </label>
            <label className="m-field"><span>Month</span>
              <input value={month} onChange={(e) => setMonth(e.target.value)} placeholder="April" />
            </label>
            <label className="m-field m-field-sm"><span>Year</span>
              <input type="number" value={year} onChange={(e) => setYear(e.target.value)} />
            </label>
          </div>
          <label className="m-field">
            <span>Caption</span>
            <textarea rows="3" value={caption} onChange={(e) => setCaption(e.target.value)} placeholder="A few words about this trip." />
          </label>
        </div>
      </form>
    </div>
  );
}

function ProfileSheet({ open, initial, onClose, onSave, busy }) {
  const [display, setDisplay] = uS("");
  const [bio, setBio] = uS("");
  const [err, setErr] = uS("");
  uE(() => {
    if (!open) return;
    setDisplay(initial?.display || "");
    setBio(initial?.bio || "");
    setErr("");
  }, [open, initial]);
  if (!open) return null;
  const submit = async (e) => {
    e && e.preventDefault && e.preventDefault();
    setErr("");
    try { await onSave({ display: display.trim(), bio: bio.trim() }); }
    catch (e) { setErr(e.message || "Error"); }
  };
  return (
    <div className="m-sheet-wrap" onClick={onClose}>
      <form className="m-sheet" onClick={(e) => e.stopPropagation()} onSubmit={submit}>
        <div className="m-sheet-hd">
          <button type="button" className="m-sheet-x" onClick={onClose}>Cancel</button>
          <div className="m-sheet-t">Edit profile</div>
          <button type="submit" className="m-sheet-ok" disabled={busy}>{busy ? "…" : "Save"}</button>
        </div>
        <div className="m-sheet-body">
          {err && <div className="m-err">{err}</div>}
          <label className="m-field"><span>Display name</span>
            <input autoFocus value={display} onChange={(e) => setDisplay(e.target.value)} maxLength={64} />
          </label>
          <label className="m-field"><span>Bio</span>
            <textarea rows="4" value={bio} onChange={(e) => setBio(e.target.value)} maxLength={280} placeholder="A line about you." />
          </label>
        </div>
      </form>
    </div>
  );
}

function Fab({ onClick, label }) {
  return (
    <button className="m-fab" onClick={onClick} aria-label={label}>
      <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M12 5v14M5 12h14" strokeLinecap="round" />
      </svg>
    </button>
  );
}

function AuthPage({ mode, onDone, onSwitch }) {
  const [username, setUsername] = uS("");
  const [password, setPassword] = uS("");
  const [busy, setBusy] = uS(false);
  const [err, setErr] = uS("");
  const submit = async (e) => {
    e.preventDefault();
    setBusy(true); setErr("");
    try {
      const user = mode === "signup"
        ? await API.signup(username.trim().toLowerCase(), password)
        : await API.login(username.trim().toLowerCase(), password);
      onDone(user);
    } catch (e) { setErr(e.message || "Error"); setBusy(false); }
  };
  return (
    <div className="m-auth">
      <div className="m-auth-mark">◐</div>
      <h1 className="m-auth-t">basalt</h1>
      <div className="m-auth-s">{mode === "signup" ? "Create your gallery" : "Welcome back"}</div>
      <form className="m-auth-form" onSubmit={submit}>
        {err && <div className="m-err">{err}</div>}
        <label className="m-field">
          <span>Username</span>
          <input
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            autoCapitalize="none"
            autoCorrect="off"
            spellCheck="false"
            placeholder="your-handle"
            required
          />
        </label>
        <label className="m-field">
          <span>Password</span>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            minLength={8}
            required
          />
        </label>
        <button type="submit" className="m-btn-primary" disabled={busy}>
          {busy ? "…" : (mode === "signup" ? "Create account" : "Log in")}
        </button>
      </form>
      <div className="m-auth-alt">
        {mode === "signup" ? (
          <>Have an account? <a href="#" onClick={(e) => { e.preventDefault(); onSwitch("login"); }}>Log in</a></>
        ) : (
          <>No account yet? <a href="#" onClick={(e) => { e.preventDefault(); onSwitch("signup"); }}>Sign up</a></>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  MobileHeader, ProfileStrip, TabStrip, AlbumsList, AllPhotosGrid,
  AlbumView, PhotoViewer, AlbumSheet, ProfileSheet, Fab, AuthPage,
});
