/* global React, Icon, Photo, useReveal, useI18n, IMG, PORTFOLIO_ITEMS */
// Dentertain — Portfolio (Alben: Kachel- & Dock-Ansicht + Galerie-Lightbox)

/* [Dentertain-Server-Patch] Portfolios kommen dynamisch aus dem Backend
   (GET /api/portfolio → { albums }). Ein Album = { id,title,place,year,cover,
   media:[{id,kind:'photo'|'video',img,videoUrl?,h}] }. Solange nichts gepflegt
   ist, werden die Demo-Bilder (PORTFOLIO_ITEMS aus shell.jsx) als 1-Bild-Alben
   angezeigt. Diese Datei ist ein getrackter Patch — Baseline:
   deploy/patches/portfolio.jsx.design-original; nach Design-Re-Import erneut
   anwenden (deploy/apply-server-patches.sh). Design/Motion strikt nach
   docs/WEBSITE-DESIGN-SYSTEM.md (Blush, --ease-out, bestehende Klassen). */

// Bild-URL auflösen: echte Uploads (/media/… oder http…) direkt, sonst Unsplash-ID (Demo).
const pfImg = (src, w) => {
  const s = typeof src === "string" ? src : (src && src.img);
  /* Eigene /media-Bilder: Breite serverseitig verkleinern (/media/w/<b>/<datei>) —
     vorher lief das Kamera-Original in Dock (200px) UND Raster UND Filmstreifen. */
  if (typeof s === "string" && s.indexOf("/media/") === 0 && w && /\.(jpe?g|png|webp)$/i.test(s)) {
    const wr = w <= 200 ? 200 : w <= 900 ? 900 : w <= 1600 ? 1600 : 1800;
    return "/media/w/" + wr + "/" + s.slice(7);
  }
  if (typeof s === "string" && (s.charAt(0) === "/" || /^https?:/.test(s))) return s;
  return IMG.base(s, w);
};
// YouTube/Vimeo-Link → Embed-URL (für den Galerie-Player).
const pfEmbed = (url) => {
  if (!url) return null;
  let m = url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([\w-]{11})/);
  if (m) return `https://www.youtube.com/embed/${m[1]}?autoplay=1&rel=0`;
  m = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
  if (m) return `https://player.vimeo.com/video/${m[1]}?autoplay=1`;
  return null;
};
const pfCover = (a) => a.cover || (a.media && a.media[0] && a.media[0].img) || "";
const pfCount = (a) => {
  const m = a.media || [];
  const f = m.filter((x) => x.kind !== "video").length, v = m.filter((x) => x.kind === "video").length;
  return [f ? f + (f === 1 ? " Foto" : " Fotos") : "", v ? v + (v === 1 ? " Film" : " Filme") : ""].filter(Boolean).join(" · ");
};
// Demo-Fallback: die alten Einzelbilder als 1-Medium-Alben verpacken.
const pfDemoAlbums = () => (window.PORTFOLIO_ITEMS || []).map((it) => ({
  id: "demo-" + it.id, title: it.title, place: it.place, year: it.year, cover: it.img,
  media: [{ id: "dm-" + it.id, kind: it.type === "video" ? "video" : "photo", img: it.img, videoUrl: "", h: it.h || 1 }],
}));

// ===== macOS Dock mit Magnification (generisch: cards mit {id,img,title,isVideo}) =====
// Touch (Handy): Finger über die Leiste ziehen = Item unterm Finger vergrößert sich
// und wird live als Haupt-Preview (Stage) angezeigt; kurzes Antippen öffnet.
const Dock = ({ items, activeId, onActive, onOpen }) => {
  const dockRef = React.useRef(null);
  const itemsRef = React.useRef([]);
  const rafRef = React.useRef(0);
  const touchRef = React.useRef(null);
  const stateRef = React.useRef({});
  stateRef.current = { items, onActive, onOpen };
  const { t } = useI18n();
  const coarse = window.matchMedia && window.matchMedia("(pointer: coarse)").matches;

  React.useEffect(() => {
    const dock = dockRef.current;
    if (!dock) return;
    const reset = () => { delete dock.dataset.scrub; itemsRef.current.forEach((el) => { if (el) { el.style.transform = "scale(1) translateY(0)"; el.style.zIndex = 1; } }); };
    // Magnification um x — Touch kräftiger (der Finger verdeckt das Item)
    const apply = (x, y, touch) => {
      const dockRect = dock.getBoundingClientRect();
      const insideY = touch || (y >= dockRect.top - 40 && y <= dockRect.bottom + 40);
      const reach = touch ? 96 : 200;
      const boost = touch ? 1.4 : 1.2;
      itemsRef.current.forEach((el) => {
        if (!el) return;
        if (!insideY) { el.style.transform = "scale(1) translateY(0)"; el.style.zIndex = 1; return; }
        const r = el.getBoundingClientRect();
        const center = r.left + r.width / 2;
        const distance = Math.abs(x - center);
        const norm = Math.max(0, 1 - distance / reach);
        const eased = Math.pow(norm, 1.4);
        el.style.transform = `scale(${1 + eased * boost}) translateY(${-(eased * (touch ? 20 : 18))}px)`;
        el.style.zIndex = Math.round((1 + eased * boost) * 10);
      });
    };
    const onMove = (e) => { const mx = e.clientX, my = e.clientY; cancelAnimationFrame(rafRef.current); rafRef.current = requestAnimationFrame(() => apply(mx, my, false)); };
    const onLeave = () => reset();

    // --- Touch-Scrubbing ---
    const itemAt = (x) => {
      let best = null, bestD = Infinity;
      itemsRef.current.forEach((el, i) => {
        if (!el) return;
        const r = el.getBoundingClientRect();
        const d = Math.abs(x - (r.left + r.width / 2));
        if (d < bestD) { bestD = d; best = i; }
      });
      return best;
    };
    const scrub = (rawX) => {
      const s = stateRef.current;
      // Finger-X auf die Item-Spanne clampen — sonst endet die Magnification, wenn
      // der Finger über das Leisten-Ende hinausrutscht (Items sind zentriert)
      let first = null, last = null;
      itemsRef.current.forEach((el) => { if (el) { if (!first) first = el; last = el; } });
      let x = rawX;
      if (first && last) {
        const a = first.getBoundingClientRect(), b = last.getBoundingClientRect();
        x = Math.max(a.left + a.width / 2, Math.min(b.left + b.width / 2, rawX));
      }
      const idx = itemAt(x);
      if (idx == null || !s.items[idx]) return;
      apply(x, 0, true);
      // data-Attribut statt Klasse: überlebt Reacts Re-Render (className ist React-verwaltet)
      dock.dataset.scrub = "1";
      const st = touchRef.current;
      if (st && st.lastId !== s.items[idx].id) { st.lastId = s.items[idx].id; s.onActive(s.items[idx].id); }
    };
    const onTouchStart = (e) => {
      const t0 = e.touches[0];
      touchRef.current = { x: t0.clientX, y: t0.clientY, moved: false, t: Date.now(), lastId: null };
      scrub(t0.clientX);
    };
    const onTouchMove = (e) => {
      if (!touchRef.current) return;
      const t0 = e.touches[0];
      if (Math.abs(t0.clientX - touchRef.current.x) > 6 || Math.abs(t0.clientY - touchRef.current.y) > 6) touchRef.current.moved = true;
      e.preventDefault(); // wir scrubben — die Seite darf nicht scrollen
      cancelAnimationFrame(rafRef.current);
      rafRef.current = requestAnimationFrame(() => scrub(t0.clientX));
    };
    const onTouchEnd = (e) => {
      const st = touchRef.current;
      touchRef.current = null;
      reset();
      if (st && !st.moved && Date.now() - st.t < 350) {
        const x = (e.changedTouches[0] || {}).clientX;
        const idx = itemAt(x);
        const s = stateRef.current;
        if (idx != null && s.items[idx]) { e.preventDefault(); s.onOpen(s.items[idx]); } // Tap = öffnen (verhindert den Doppel-Click)
      }
    };
    window.addEventListener("mousemove", onMove);
    dock.addEventListener("mouseleave", onLeave);
    dock.addEventListener("touchstart", onTouchStart, { passive: true });
    dock.addEventListener("touchmove", onTouchMove, { passive: false });
    dock.addEventListener("touchend", onTouchEnd);
    dock.addEventListener("touchcancel", onLeave);
    return () => {
      window.removeEventListener("mousemove", onMove);
      dock.removeEventListener("mouseleave", onLeave);
      dock.removeEventListener("touchstart", onTouchStart);
      dock.removeEventListener("touchmove", onTouchMove);
      dock.removeEventListener("touchend", onTouchEnd);
      dock.removeEventListener("touchcancel", onLeave);
      cancelAnimationFrame(rafRef.current);
    };
  }, [items.length]);

  return (
    <div className="portfolio-dock-wrap">
      <div className="portfolio-dock-hint">{coarse ? t("pf.dock.hint.touch") : t("pf.dock.hint")}</div>
      <div className="portfolio-dock" ref={dockRef}>
        {items.map((it, i) => (
          <div key={it.id} ref={(el) => (itemsRef.current[i] = el)}
            className={`dock-item ${activeId === it.id ? "active" : ""}`}
            onMouseEnter={() => onActive(it.id)} onClick={() => onOpen(it)} title={it.title}>
            <img src={pfImg(it.img, 200)} alt={it.title} />
            <span className="dock-item__label">{it.title}</span>
            <span className="dock-item__type"><Icon name={it.isVideo ? "play" : "image"} size={10} /></span>
          </div>
        ))}
      </div>
    </div>
  );
};

// ===== Featured Stage (Dock-Ansicht) =====
const Stage = ({ items, activeId, onOpen, openLabel }) => {
  const active = items.find((i) => i.id === activeId) || items[0];
  /* NUR aktives + vorheriges Bild mounten (Crossfade braucht genau 2). Vorher lagen
     ALLE Items voll dekodiert im DOM — bei offenem Album ~40 Full-Res-Bitmaps allein
     hier → zusammen mit Dock+Raster >700 MB → iOS-Safari killt den Tab. */
  const lastRef = React.useRef(null);
  const prevRef = React.useRef(null);
  if (active && lastRef.current !== active.id) { prevRef.current = lastRef.current; lastRef.current = active.id; }
  if (!active) return null;
  const mounted = items.filter((it) => it.id === active.id || it.id === prevRef.current);
  return (
    <div className="portfolio-stage portfolio-view-fade" key={items.map((i) => i.id).join(",")}>
      <div className="portfolio-stage__viewer">
        {mounted.map((it) => (<Photo key={it.id} src={pfImg(it.img, 1600)} className={`${it.id === activeId ? "active" : ""}`} />))}
        <div className="portfolio-stage__overlay"></div>
        <div className="portfolio-stage__video-pill">
          <Icon name={active.isVideo ? "play" : "image"} size={11} /> {active.badge}
        </div>
        <div className="portfolio-stage__caption">
          <div>
            <div className="portfolio-stage__caption-meta">{active.sub}</div>
            <h3 className="portfolio-stage__caption-title">{active.title}</h3>
          </div>
          <button className="btn btn--inverse" onClick={() => onOpen(active)} style={{ whiteSpace: "nowrap" }}>
            {openLabel} <Icon name={active.isVideo ? "play" : "arrow-right"} size={14} />
          </button>
        </div>
      </div>
    </div>
  );
};

// ===== Kachel-Ansicht (Album-Cover, quadratisch) =====
const Tiles = ({ cards, onOpen }) => (
  <div className="portfolio-tiles-wrap portfolio-view-fade">
    <div className="portfolio-tiles">
      {cards.map((it) => (
        <a key={it.id} className="portfolio-tile" onClick={() => onOpen(it)}>
          <img src={pfImg(it.img, 900)} alt={it.title} />
          <span className="portfolio-tile__type"><Icon name={it.isVideo ? "play" : "image"} size={10} />{it.badge}</span>
          <div className="portfolio-tile__overlay">
            <div>
              <div className="portfolio-tile__meta">{it.sub}</div>
              <div className="portfolio-tile__title">{it.title}</div>
            </div>
          </div>
        </a>
      ))}
    </div>
  </div>
);

// ===== Masonry-Raster (unter der Dock-Ansicht) =====
const PortfolioGrid = ({ cards, onOpen, kkey }) => (
  <div className="portfolio-grid fade-in" key={kkey}>
    {cards.map((it) => (
      <a key={it.id} className="portfolio-grid__item" onClick={() => onOpen(it)} style={{ aspectRatio: `1 / ${it.h || 1}` }}>
        {/* Beim Laden echtes Seitenverhältnis übernehmen — gespeicherte h sind oft 1
            (Quadrat), breite 16:9-Bilder ließen sonst einen toten Streifen unter dem Bild */}
        <img src={pfImg(it.img, 900)} alt={it.title} onLoad={(e) => {
          const im = e.target;
          if (im.naturalWidth > 0) {
            const a = im.closest(".portfolio-grid__item");
            if (a) a.style.aspectRatio = "1 / " + (im.naturalHeight / im.naturalWidth).toFixed(4);
          }
        }} />
        <span className="portfolio-grid__type"><Icon name={it.isVideo ? "play" : "image"} size={10} />{it.badge}</span>
        <div className="portfolio-grid__item-overlay">
          <div>
            <div className="portfolio-grid__item-meta">{it.sub}</div>
            <div className="portfolio-grid__item-title">{it.title}</div>
          </div>
        </div>
      </a>
    ))}
  </div>
);

// ===== Galerie-Lightbox: durch die Medien eines Albums blättern (+ Video) =====
const Gallery = ({ album, index, onClose, setIndex }) => {
  const media = (album && album.media) || [];
  const cur = media[index];
  const swipeRef = React.useRef(null);
  const thumbsRef = React.useRef(null);
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowLeft") setIndex((index - 1 + media.length) % media.length);
      if (e.key === "ArrowRight") setIndex((index + 1) % media.length);
    };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = album ? "hidden" : "";
    return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [album, index, media.length, onClose, setIndex]);
  // Aktives Thumbnail im Filmstreifen zentrieren
  React.useEffect(() => {
    const c = thumbsRef.current;
    if (!c) return;
    const el = c.children[index];
    if (!el) return;
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    c.scrollTo({ left: Math.max(0, el.offsetLeft - (c.clientWidth - el.offsetWidth) / 2), behavior: reduce ? "auto" : "smooth" });
  }, [index, album]);
  if (!album) return null;
  // Wischen blättert (Handy) — horizontal ≥ 40px und dominant gegenüber vertikal.
  // AUSNAHME: Wisch auf dem Filmstreifen scrollt nur die Leiste, blättert NIE.
  const onTouchStart = (e) => {
    if (e.target && e.target.closest && e.target.closest(".lightbox__thumbs")) { swipeRef.current = null; return; }
    const t0 = e.touches[0]; swipeRef.current = { x: t0.clientX, y: t0.clientY };
  };
  const onTouchEnd = (e) => {
    const st = swipeRef.current; swipeRef.current = null;
    if (!st || media.length < 2) return;
    const t0 = e.changedTouches[0];
    const dx = t0.clientX - st.x, dy = t0.clientY - st.y;
    if (Math.abs(dx) >= 40 && Math.abs(dx) > Math.abs(dy) * 1.5) {
      setIndex(dx < 0 ? (index + 1) % media.length : (index - 1 + media.length) % media.length);
    }
  };
  return (
    <div className={`lightbox ${album ? "open" : ""}`} onClick={onClose} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
      <div className="lightbox__bar">
        <span className="lightbox__count">{media.length > 1 ? `${index + 1} / ${media.length}` : ""}</span>
        <button className="lightbox__close" onClick={onClose} aria-label="Schließen"><Icon name="x" size={20} /></button>
      </div>
      {media.length > 1 ? (
        <>
          <button className="lightbox__nav lightbox__nav--prev" aria-label="Zurück" onClick={(e) => { e.stopPropagation(); setIndex((index - 1 + media.length) % media.length); }}><Icon name="arrow-left" size={22} /></button>
          <button className="lightbox__nav lightbox__nav--next" aria-label="Weiter" onClick={(e) => { e.stopPropagation(); setIndex((index + 1) % media.length); }}><Icon name="arrow-right" size={22} /></button>
        </>
      ) : null}
      <div className="lightbox__inner" onClick={(e) => e.stopPropagation()}>
        {cur && cur.kind === "video" && pfEmbed(cur.videoUrl) ? (
          <div className="lightbox__media" style={{ width: "min(1100px, 92vw)", aspectRatio: "16 / 9" }}>
            <iframe key={cur.id} src={pfEmbed(cur.videoUrl)} title={album.title} style={{ width: "100%", height: "100%", border: 0, borderRadius: 8 }} allow="autoplay; fullscreen; picture-in-picture" allowFullScreen></iframe>
          </div>
        ) : cur ? (
          <img key={cur.id} src={pfImg(cur.img, 1800)} alt={album.title} className="lightbox__media" />
        ) : null}
        <div className="lightbox__caption">{album.title}{album.place ? " · " + album.place : ""}{album.year ? " · " + album.year : ""}</div>
        {media.length > 1 ? (
          <div className="lightbox__thumbs" ref={thumbsRef}>
            {media.map((m, i) => (
              <button key={m.id} className={`lightbox__thumb ${i === index ? "active" : ""}`} onClick={() => setIndex(i)} aria-label={`Bild ${i + 1}`} aria-current={i === index}>
                <img src={pfImg(m.img, 200)} alt="" />
              </button>
            ))}
          </div>
        ) : null}
      </div>
    </div>
  );
};

const PortfolioPage = ({ onNavigate }) => {
  useReveal();
  const { t } = useI18n();
  const [albums, setAlbums] = React.useState(() => pfDemoAlbums());
  React.useEffect(() => {
    let alive = true;
    fetch("/api/portfolio", { credentials: "same-origin" })
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => { if (alive && d && Array.isArray(d.albums) && d.albums.length) setAlbums(d.albums); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  const [view, setView] = React.useState("dock");     // "dock" | "tiles"
  const [openAlbumId, setOpenAlbumId] = React.useState(null); // Dock-Drill-down
  const [activeId, setActiveId] = React.useState(null);
  const [gallery, setGallery] = React.useState(null);  // { albumId, index }

  const openAlbum = albums.find((a) => a.id === openAlbumId) || null;

  // Karten für die aktuelle Ebene (Album-Cover ODER Medien eines Albums)
  const albumCards = albums.map((a) => ({ id: a.id, img: pfCover(a), title: a.title, sub: [a.place, a.year].filter(Boolean).join(" · "), badge: pfCount(a) || "leer", isVideo: false, h: (a.media && a.media[0] && a.media[0].h) || 1, _album: a }));
  const mediaCards = openAlbum ? (openAlbum.media || []).map((m) => ({ id: m.id, img: m.img, title: openAlbum.title, sub: [openAlbum.place, openAlbum.year].filter(Boolean).join(" · "), badge: m.kind === "video" ? t("pf.tab.video") : t("pf.tab.photo"), isVideo: m.kind === "video", h: m.h || 1, _media: m })) : [];
  const dockCards = openAlbum ? mediaCards : albumCards;

  React.useEffect(() => {
    if (dockCards.length && !dockCards.find((c) => c.id === activeId)) setActiveId(dockCards[0].id);
  }, [openAlbumId, albums, dockCards, activeId]);

  const openGallery = (albumId, index) => setGallery({ albumId, index: Math.max(0, index) });
  // Dock/Grid/Stage-Klick: Ebene 0 → ins Album; Ebene 1 → Galerie beim geklickten Medium.
  const onOpenCard = (card) => {
    if (!openAlbum) { setOpenAlbumId(card.id); setActiveId(null); }
    else { const idx = (openAlbum.media || []).findIndex((m) => m.id === card.id); openGallery(openAlbum.id, idx < 0 ? 0 : idx); }
  };
  const onTileOpen = (card) => openGallery(card.id, 0); // Kachel = Album-Cover → Galerie ab Bild 1

  const galleryAlbum = gallery ? albums.find((a) => a.id === gallery.albumId) : null;

  return (
    <div className="portfolio-page page-enter">
      <section className="portfolio-hero">
        <div className="container">
          <span className="eyebrow solo">{t("pf.eyebrow")}</span>
          <h1 className="h-display" style={{ marginTop: 16 }}>{t("pf.title.a")} <em>{t("pf.title.b")}</em></h1>
          <p className="lead" style={{ margin: "28px auto 0" }}>{t("pf.lead")}</p>
          <div className="portfolio-view-row">
            <div className="portfolio-view-toggle" role="tablist" aria-label="View">
              <button role="tab" className={`portfolio-view-btn ${view === "dock" ? "active" : ""}`} onClick={() => { setView("dock"); setOpenAlbumId(null); }}>
                <Icon name="film" size={14} stroke={1.6} />{t("pf.view.dock")}
              </button>
              <button role="tab" className={`portfolio-view-btn ${view === "tiles" ? "active" : ""}`} onClick={() => setView("tiles")}>
                <Icon name="image" size={14} stroke={1.6} />{t("pf.view.grid")}
              </button>
            </div>
          </div>
        </div>
      </section>

      {view === "dock" ? (
        <>
          {openAlbum ? (
            <div className="container" style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 6 }}>
              <button className="btn btn--ghost" onClick={() => { setOpenAlbumId(null); setActiveId(null); }}>
                <Icon name="arrow-left" size={14} /> Alle Portfolios
              </button>
              <span className="mono" style={{ color: "var(--fg-3)" }}>{openAlbum.title}{openAlbum.place ? " · " + openAlbum.place : ""}{openAlbum.year ? " · " + openAlbum.year : ""}</span>
            </div>
          ) : null}

          {dockCards.length ? (
            <>
              <Stage items={dockCards} activeId={activeId} onOpen={onOpenCard} openLabel={openAlbum ? "Ansehen" : "Portfolio öffnen"} />
              <Dock items={dockCards} activeId={activeId} onActive={setActiveId} onOpen={onOpenCard} />
              <section className="tight">
                <div className="container">
                  <div className="section-head section-head--row reveal">
                    <div className="section-head__group">
                      <span className="eyebrow">{t("pf.more.eyebrow")}</span>
                      <h2 className="h1">{openAlbum ? <>Alle <em>Aufnahmen</em></> : <>{t("pf.more.title.a")} <em>{t("pf.more.title.b")}</em></>}</h2>
                    </div>
                    <p className="p" style={{ maxWidth: 380 }}>{openAlbum ? "Jedes Bild öffnet die Galerie." : (window.matchMedia && window.matchMedia("(pointer: coarse)").matches ? t("pf.more.sub.touch") : t("pf.more.sub"))}</p>
                  </div>
                </div>
                <PortfolioGrid cards={dockCards} onOpen={onOpenCard} kkey={openAlbumId || "root"} />
              </section>
            </>
          ) : (
            <div className="container" style={{ textAlign: "center", padding: "60px 0", color: "var(--fg-3)" }}>Dieses Portfolio hat noch keine Medien.</div>
          )}
        </>
      ) : (
        <Tiles cards={albumCards} onOpen={onTileOpen} />
      )}

      <section className="cta-band">
        <div className="cta-band__inner reveal">
          <span className="eyebrow solo">{t("ct.eyebrow")}</span>
          <h2 className="cta-band__title">{t("pf.cta.title.a")} <em>{t("pf.cta.title.b")}</em></h2>
          <p className="cta-band__sub">{t("pf.cta.sub")}</p>
          <button className="btn btn--dark" onClick={() => onNavigate("contact")}>{t("pf.cta.btn")} <Icon name="arrow-right" size={14} /></button>
        </div>
      </section>

      <Gallery album={galleryAlbum} index={gallery ? gallery.index : 0} onClose={() => setGallery(null)} setIndex={(i) => setGallery((g) => (g ? { ...g, index: i } : g))} />
    </div>
  );
};

Object.assign(window, { PortfolioPage });
