/* global React */
// Inward concave UGC carousel. Cards form a "U" arc — center recedes,
// edges come forward and tilt inward. A bright green vertical beam
// pulses behind the centermost card.

const { useEffect, useRef, useState } = React;

// ─── Tile metadata ──────────────────────────────────────────────────────────
// Each tile has a persistent id; user-uploaded videos are stored in IndexedDB
// keyed by that id, so refreshing the page keeps the videos.
const TILES = [
  { id: "tile-1", tone: "halftone" },
  { id: "tile-2", tone: "mono"     },
  { id: "tile-3", tone: "dim"      },
  { id: "tile-4", tone: "warm"     },
  { id: "tile-5", tone: "dim"      },
  { id: "tile-6", tone: "cool"     },
  { id: "tile-7", tone: "vivid"    },
];

// ─── IndexedDB helpers ──────────────────────────────────────────────────────
// localStorage can't hold MP4 blobs (5–10MB cap, string-only). IndexedDB can.
function idbOpen() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open("tile-videos", 1);
    req.onupgradeneeded = (e) => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains("files")) db.createObjectStore("files");
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror   = () => reject(req.error);
  });
}
async function idbSave(id, blob) {
  const db = await idbOpen();
  return new Promise((resolve, reject) => {
    const tx = db.transaction("files", "readwrite");
    tx.objectStore("files").put(blob, id);
    tx.oncomplete = () => resolve();
    tx.onerror    = () => reject(tx.error);
  });
}
async function idbLoad(id) {
  const db = await idbOpen();
  return new Promise((resolve, reject) => {
    const tx = db.transaction("files", "readonly");
    const req = tx.objectStore("files").get(id);
    req.onsuccess = () => resolve(req.result || null);
    req.onerror   = () => reject(req.error);
  });
}
async function idbDelete(id) {
  const db = await idbOpen();
  return new Promise((resolve, reject) => {
    const tx = db.transaction("files", "readwrite");
    tx.objectStore("files").delete(id);
    tx.oncomplete = () => resolve();
    tx.onerror    = () => reject(tx.error);
  });
}

// ─── VideoSlot ───────────────────────────────────────────────────────────────
// Per-tile upload + playback. Click anywhere on an empty tile to open a
// system file picker, or drag-drop a video file onto it. Hover a filled tile
// to reveal a small "Replace / Clear" toolbar.
function VideoSlot({ id }) {
  const [url, setUrl]       = useState(null);
  const [hover, setHover]   = useState(false);
  const [drag, setDrag]     = useState(false);
  const inputRef            = useRef(null);
  const urlRef              = useRef(null);

  // Load persisted video on mount.
  useEffect(() => {
    let alive = true;
    idbLoad(id).then((blob) => {
      if (!alive || !blob) return;
      const u = URL.createObjectURL(blob);
      urlRef.current = u;
      setUrl(u);
    });
    return () => {
      alive = false;
      if (urlRef.current) URL.revokeObjectURL(urlRef.current);
    };
  }, [id]);

  const handleFile = async (file) => {
    if (!file || !file.type.startsWith("video/")) return;
    await idbSave(id, file);
    if (urlRef.current) URL.revokeObjectURL(urlRef.current);
    const u = URL.createObjectURL(file);
    urlRef.current = u;
    setUrl(u);
  };

  const onPick = (e) => handleFile(e.target.files && e.target.files[0]);
  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    handleFile(e.dataTransfer.files && e.dataTransfer.files[0]);
  };
  const onDragOver  = (e) => { e.preventDefault(); setDrag(true); };
  const onDragLeave = ()  => setDrag(false);

  const onClear = async (e) => {
    e.stopPropagation();
    await idbDelete(id);
    if (urlRef.current) { URL.revokeObjectURL(urlRef.current); urlRef.current = null; }
    setUrl(null);
  };
  const onBrowse = (e) => {
    e.stopPropagation();
    inputRef.current && inputRef.current.click();
  };

  const isDev = typeof window !== "undefined" && /^(localhost|127\.|0\.0\.0\.0|\[?::1)/.test(window.location.hostname);

  return (
    <div
      className={`video-slot ${drag ? "is-drag" : ""} ${isDev ? "" : "video-slot--public"}`}
      onClick={!isDev || url ? undefined : onBrowse}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      onDragOver={isDev ? onDragOver : undefined}
      onDragLeave={isDev ? onDragLeave : undefined}
      onDrop={isDev ? onDrop : undefined}
    >
      {url && (
        <video
          className="tile-video"
          src={url}
          muted
          autoPlay
          loop
          playsInline
          preload="auto"
        ></video>
      )}
      {!url && isDev && (
        <div className="video-empty">
          <div className="video-empty-plus">+</div>
          <div className="video-empty-label">Browse · Drop MP4</div>
        </div>
      )}
      {url && hover && isDev && (
        <div className="video-tools" onClick={(e) => e.stopPropagation()}>
          <button onClick={onBrowse} title="Replace video">↻</button>
          <button onClick={onClear}  title="Remove video">✕</button>
        </div>
      )}
      {isDev && (
        <input
          ref={inputRef}
          type="file"
          accept="video/mp4,video/quicktime,video/*"
          style={{ display: "none" }}
          onChange={onPick}
        />
      )}
    </div>
  );
}

// ─── Carousel ───────────────────────────────────────────────────────────────
function InwardCarousel() {
  const tilesRef = useRef([]);
  const startRef = useRef(performance.now());
  const rafRef = useRef(0);

  // U-arc geometry — half-cylinder feel. Edges come forward and tilt
  // sharply inward; center recedes. Larger RADIUS opens gaps between tiles.
  const FAN_DEG    = 156;    // angular spread of the visible arc
  const RADIUS     = 1120;   // depth at center of the bowl (bigger = more gap)
  const Y_LIFT     = 0;      // 0 = flat baseline
  const ROT_FACTOR = 1.0;    // full inward tilt — face the center axis
  const LOOP_SECS  = 26;     // full cycle (right → left)
  const LOOP_SECS_M = 24;     // slightly faster on mobile
  const FADE_FRAC  = 0.05;   // fade at the wrap boundary

  const N = TILES.length;

  // Detect mobile and use compact geometry so tiles don't overflow a 390px viewport.
  const isMobile = typeof window !== "undefined" && window.matchMedia("(max-width: 600px)").matches;
  const FAN_DEG_M    = isMobile ? 150  : FAN_DEG;
  const RADIUS_M     = isMobile ? 620  : RADIUS;

  useEffect(() => {
    const apply = () => {
      const elapsed = (performance.now() - startRef.current) / 1000;
      const t = (elapsed / (isMobile ? LOOP_SECS_M : LOOP_SECS)) % 1;
      for (let i = 0; i < N; i++) {
        let p = ((i / N) - t + 1) % 1;
        const angleDeg = FAN_DEG_M / 2 - p * FAN_DEG_M;
        const rad = (angleDeg * Math.PI) / 180;
        const x = RADIUS_M * Math.sin(rad);
        const y = -Y_LIFT * (1 - Math.cos(rad));
        const z = -RADIUS_M * Math.cos(rad);
        const rot = -angleDeg * ROT_FACTOR;

        let opacity = 1;
        if (p < FADE_FRAC) opacity = p / FADE_FRAC;
        else if (p > 1 - FADE_FRAC) opacity = (1 - p) / FADE_FRAC;

        const el = tilesRef.current[i];
        if (el) {
          el.style.transform =
            `translate(-50%, -50%) ` +
            `translate3d(${x.toFixed(1)}px, ${y.toFixed(1)}px, ${z.toFixed(1)}px) ` +
            `rotateY(${rot.toFixed(2)}deg)`;
          el.style.opacity = opacity.toFixed(3);
          el.style.zIndex = String(Math.round(1000 - Math.abs(angleDeg) * 5));
        }
      }
      rafRef.current = requestAnimationFrame(apply);
    };
    rafRef.current = requestAnimationFrame(apply);
    return () => cancelAnimationFrame(rafRef.current);
  }, []);

  return (
    <div className="stage" aria-label="Selected work">
      {/* Soft floor glow under the arc */}
      <div className="stage-floor" aria-hidden></div>

      <div className="track">
        {TILES.map((c, i) => (
          <div
            className={`tile tone-${c.tone}`}
            key={c.id}
            ref={(el) => { tilesRef.current[i] = el; }}
            style={{ opacity: 0 }}
          >
            <div className="frame">
              {/* Upload + playback. Click empty tile to browse, drop a file,
                  or hover a filled tile to replace/clear. Persists in IDB. */}
              <VideoSlot id={c.id} />
              {/* Treatment overlays — give each tile its visual character
                  even before a video is loaded. */}
              <div className="tone-overlay" aria-hidden></div>
              <div className="halftone" aria-hidden></div>
              <div className="vignette" aria-hidden></div>
              <div className="sheen" aria-hidden></div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

window.InwardCarousel = InwardCarousel;
window.VideoSlot = VideoSlot;
