import * as THREE from 'three';

// Crate-digging: a self-contained inspect scene. Enter on a crate → records stand packed in a
// bin; scroll/drag riffles a damped "cursor" through them; each sleeve hinges forward at its
// bottom edge as you pass it; tap the front sleeve to pull it out and inspect (price/condition/
// add-to-cart). Procedural fwip/thunk audio — no asset files.
// Mechanics + tuned numbers ported back from THRIFTGOD's evolved fork (docs/DIG_FLIP_HANDOVER.md).

const MAXFLIP = 1.95, BACK = -0.12;
// sleeve [width, height, thickness] per format — records, CDs, DVDs, tapes all riffle the same
const FMT = { lp: [0.31, 0.31, 0.0035], cd: [0.142, 0.125, 0.010], dvd: [0.136, 0.19, 0.014],
              vhs: [0.105, 0.187, 0.025], cass: [0.11, 0.07, 0.017] };

export function createDig(renderer, { onClose } = {}) {
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x0a0a0c);
  const camera = new THREE.PerspectiveCamera(45, (innerWidth / innerHeight) || 1, 0.01, 50);
  camera.position.set(0, 0.5, 0.92);
  camera.lookAt(0, 0.12, -0.2);
  addEventListener('resize', () => {   // index.html's resize handler only feeds the walk camera
    camera.aspect = (innerWidth / innerHeight) || 1; camera.updateProjectionMatrix();
  });

  scene.add(new THREE.AmbientLight(0xffffff, 0.45));
  const spot = new THREE.SpotLight(0xfff2e6, 26, 5, 0.7, 0.5, 1.2);
  spot.position.set(0.4, 1.5, 0.9); spot.target.position.set(0, 0, -0.3);
  scene.add(spot, spot.target);
  const floor = new THREE.Mesh(new THREE.BoxGeometry(0.46, 0.02, 1.1),
    new THREE.MeshStandardMaterial({ color: 0x16161a, roughness: 0.9 }));
  floor.position.set(0, -0.011, -0.45); scene.add(floor);

  const loader = new THREE.TextureLoader(); loader.crossOrigin = 'anonymous';
  let recs = [], cursor = 0, vel = 0, active = false, pulled = null, lastFloor = -1;

  function el(tag, cls, txt) { const e = document.createElement(tag); e.className = cls; if (txt) e.textContent = txt; return e; }
  const css = el('style'); css.textContent = `
    .dg-hint{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);background:rgba(0,0,0,.6);color:#eee;padding:6px 14px;border-radius:20px;font:13px system-ui;pointer-events:none;display:none}
    .dg-cur{position:fixed;left:50%;top:22px;transform:translateX(-50%);color:#fff;font:500 16px system-ui;text-shadow:0 1px 4px #000;pointer-events:none;text-align:center;max-width:80%;display:none}
    .dg-panel{position:fixed;right:6%;top:50%;transform:translateY(-50%);width:280px;background:rgba(16,16,20,.94);border:1px solid #333;border-radius:12px;padding:18px;color:#eee;font:14px system-ui;display:none;z-index:7}
    .dg-panel h3{margin:.1em 0;font-weight:500;color:#ff5db1}
    .dg-panel .meta{color:#aaa;font-size:13px;margin:4px 0 10px}
    .dg-panel .price{font-size:24px;font-weight:500}
    .dg-panel button{margin-top:14px;width:100%;padding:10px;border:0;border-radius:8px;background:#ff5db1;color:#1a0a14;font:500 14px system-ui;cursor:pointer}
    .dg-panel .x{position:absolute;top:8px;right:13px;cursor:pointer;color:#888;font-size:18px}`;
  document.head.appendChild(css);
  const hint = el('div', 'dg-hint', 'scroll / drag to flip · click a sleeve to pull · Esc to step back');
  const curLbl = el('div', 'dg-cur');
  const panel = el('div', 'dg-panel');
  document.body.append(hint, curLbl, panel);

  // --- procedural audio ---
  let actx;
  function blip(kind) {
    try {
      actx = actx || new (window.AudioContext || window.webkitAudioContext)();
      if (kind === 'fwip') {
        const n = Math.floor(actx.sampleRate * 0.05), buf = actx.createBuffer(1, n, actx.sampleRate), d = buf.getChannelData(0);
        for (let i = 0; i < n; i++) d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / n, 2);
        const s = actx.createBufferSource(); s.buffer = buf;
        const f = actx.createBiquadFilter(); f.type = 'bandpass'; f.frequency.value = 2100; f.Q.value = 0.7;
        const g = actx.createGain(); g.gain.value = 0.22;
        s.connect(f); f.connect(g); g.connect(actx.destination); s.start();
      } else {
        const o = actx.createOscillator(); o.type = 'sine'; o.frequency.value = 105;
        const g = actx.createGain(); g.gain.setValueAtTime(0.0001, actx.currentTime);
        g.gain.exponentialRampToValueAtTime(0.5, actx.currentTime + 0.005);
        g.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + 0.28);
        o.connect(g); g.connect(actx.destination); o.start(); o.stop(actx.currentTime + 0.3);
      }
    } catch (e) {}
  }

  // the crate drawn around the stack — floor/left/right/back + a LOWER front lip you flip over
  let crate = null;
  function buildCrate(maxW, maxH, depth) {
    if (crate) scene.remove(crate);
    crate = new THREE.Group();
    // ponytail: plain wood colour — no wood texture in webstore/assets; add one + RepeatWrapping ×2 if it looks flat
    const wood = new THREE.MeshStandardMaterial({ color: 0x8a6a45, roughness: 0.85 });
    const W = maxW + 0.06, H = maxH * 0.72, D = depth + 0.10, th = 0.016;
    const add = (w, h, d, x, y, z) => { const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), wood); m.position.set(x, y, z); crate.add(m); };
    add(W, th, D, 0, -th / 2, -D / 2 + 0.05);                    // floor
    add(th, H, D, -W / 2, H / 2, -D / 2 + 0.05);                 // left
    add(th, H, D, W / 2, H / 2, -D / 2 + 0.05);                  // right
    add(W, H, th, 0, H / 2, -D + 0.05);                          // back
    add(W, H * 0.55, th, 0, H * 0.275, 0.05);                    // front lip (lower — you flip over it)
    scene.add(crate);
  }

  function buildStack(records) {
    recs.forEach(r => scene.remove(r.group)); recs = [];
    let z = -0.05, maxW = 0.31, maxH = 0.31;
    records.forEach((rec, i) => {
      const [w, h, th] = FMT[rec.fmt] || FMT.lp;
      maxW = Math.max(maxW, w); maxH = Math.max(maxH, h);
      const grp = new THREE.Group(); grp.position.set(0, 0, z);
      const mat = new THREE.MeshStandardMaterial({ color: 0x2b2b33, roughness: 0.75 });
      const mesh = new THREE.Mesh(new THREE.BoxGeometry(w, h, th), mat);
      mesh.position.y = h / 2;              // hinge at group origin = bin floor
      grp.add(mesh); grp.rotation.x = BACK;
      // secondhand records don't stand to attention — every sleeve leans its own way
      grp.rotation.z = (((i * 2654435761) >>> 0) % 100 - 50) / 1800;
      grp.position.x = (((i * 40503) >>> 0) % 100 - 50) / 12000;
      scene.add(grp);
      recs.push({ group: grp, mat, rec, angle: BACK, loaded: false, homeZ: z });
      z -= th + 0.010;                      // packed by real thickness — VHS riffles chunkier than vinyl
    });
    buildCrate(maxW, maxH, -z);
    cursor = 0; vel = 0; lastFloor = -1; loadCoversNear(); updateCur();
    fillQueue();                            // trickle-load EVERY cover so the crate fills in behind you
  }

  let fillTimer = null;
  function fillQueue() {
    clearTimeout(fillTimer);
    const next = recs.find(r => !r.loaded);
    if (!next) return;
    loadCover(next);
    fillTimer = setTimeout(fillQueue, 120);
  }

  function loadCover(r) {
    if (r.loaded) return;
    r.loaded = true;
    const url = r.rec.thumb; if (!url) return;
    loader.load(url, tx => { tx.colorSpace = THREE.SRGBColorSpace; r.mat.map = tx; r.mat.color.set(0xffffff); r.mat.needsUpdate = true; }, undefined, () => {});
  }

  function loadCoversNear() {
    const c = Math.round(cursor);
    for (let i = Math.max(0, c - 10); i < Math.min(recs.length, c + 15); i++) loadCover(recs[i]);
  }

  function updateCur() {
    const r = recs[Math.round(cursor)];
    curLbl.textContent = r ? `${r.rec.title || r.rec.sku}${r.rec.artist ? ' — ' + r.rec.artist : ''}` : 'crate\'s empty — keep browsing';
  }

  // re-pack the survivors after a sleeve leaves the crate — no gap left behind
  function repack() {
    let z = -0.05;
    recs.forEach(x => { x.homeZ = z; x.group.position.z = z; z -= (FMT[x.rec.fmt] || FMT.lp)[2] + 0.010; });
  }

  function pull() {
    const r = recs[Math.round(cursor)]; if (!r) return;
    pulled = r; r.pullT = 0; blip('thunk');
    const d = r.rec;
    panel.innerHTML = `<div class="x">✕</div><h3>${d.title || d.sku}</h3>`
      + `<div class="meta">${d.artist || ''}</div>`
      + `<div class="price">$${d.price ?? '—'}</div>`
      + `<div class="meta">condition ${d.condition || '—'}</div>`
      + `<button>Add to cart</button>`;
    panel.style.display = 'block';
    panel.querySelector('.x').onclick = unpull;
    panel.querySelector('button').onclick = () => {
      // client cart, SKU-keyed — /shop/checkout re-prices server-side and rejects sold stock
      try {
        const cart = JSON.parse(localStorage.getItem('rg_cart') || '[]');
        if (!cart.some(x => x.sku === d.sku)) {
          cart.push({ sku: d.sku, release_id: d.release_id, title: d.title, artist: d.artist, price: d.price, thumb: d.thumb });
          localStorage.setItem('rg_cart', JSON.stringify(cart));
        }
      } catch (e) {}
      const i = recs.indexOf(r);
      scene.remove(r.group); recs.splice(i, 1);
      repack();
      pulled = null; panel.style.display = 'none';
      cursor = Math.min(cursor, Math.max(0, recs.length - 1));
      curLbl.textContent = 'in your cart: ' + (d.title || d.sku);
    };
  }

  function unpull() {
    if (!pulled) return;
    pulled.group.position.set(0, 0, pulled.homeZ);
    pulled = null; panel.style.display = 'none'; updateCur();
  }

  function update(dt) {
    if (!active) return;
    vel *= Math.pow(0.0008, dt);                 // damped momentum
    cursor += vel * dt;
    if (cursor < 0) { cursor = 0; vel = 0; }
    if (cursor > recs.length - 1) { cursor = Math.max(0, recs.length - 1); vel = 0; }
    const fl = Math.floor(cursor);
    if (fl !== lastFloor) { lastFloor = fl; blip('fwip'); loadCoversNear(); updateCur(); }
    recs.forEach((r, i) => {
      const f = THREE.MathUtils.smoothstep(cursor - i, -0.4, 1.2);  // 0 upright → 1 flipped forward
      const target = BACK + f * (MAXFLIP - BACK);
      r.angle += (target - r.angle) * Math.min(1, dt * 12);
      if (r !== pulled) r.group.rotation.x = r.angle;
    });
    if (pulled) {
      pulled.pullT = Math.min(1, pulled.pullT + dt * 3);
      // arm's length on the view axis: whole cover in frame, tilted to face you, panel-side clear
      const h = (FMT[pulled.rec.fmt] || FMT.lp)[1];
      pulled.group.position.lerp(new THREE.Vector3(-0.1, 0.23 - h / 2, 0.12), Math.min(1, dt * 5));
      pulled.group.rotation.x += (-0.33 - pulled.group.rotation.x) * Math.min(1, dt * 6);
    }
  }

  // --- input (guarded by active) ---
  let dragY = null, moved = 0;
  function onWheel(e) { if (!active) return; e.preventDefault(); vel += e.deltaY * 0.012; }
  function onDown(e) { if (!active) return; dragY = e.clientY; moved = 0; }
  function onMove(e) { if (!active || dragY == null) return; const dy = e.clientY - dragY; moved += Math.abs(dy); cursor += dy * 0.02; vel = dy * 0.6; dragY = e.clientY; }
  function onUp() { if (!active) return; if (moved < 4 && !pulled) pull(); dragY = null; }
  function onKey(e) { if (!active) return; if (e.key === 'Escape') { e.stopPropagation(); if (pulled) unpull(); else close(); } }
  renderer.domElement.addEventListener('wheel', onWheel, { passive: false });
  renderer.domElement.addEventListener('pointerdown', onDown);
  renderer.domElement.addEventListener('pointermove', onMove);
  renderer.domElement.addEventListener('pointerup', onUp);
  document.addEventListener('keydown', onKey, true);

  async function open(crateId) {
    active = true; hint.style.display = 'block'; curLbl.style.display = 'block';
    camera.aspect = (innerWidth / innerHeight) || 1; camera.updateProjectionMatrix();
    try {
      const res = await fetch(`/virtual/crate/${crateId}/records`).then(r => r.json());
      buildStack(res.records || []);
    } catch (e) { curLbl.textContent = 'failed to load crate'; }
  }

  function close() {
    active = false; pulled = null;
    clearTimeout(fillTimer);
    [hint, curLbl, panel].forEach(e => e.style.display = 'none');
    recs.forEach(r => scene.remove(r.group)); recs = [];
    if (crate) { scene.remove(crate); crate = null; }
    if (onClose) onClose();
  }

  return { scene, camera, open, update, close, get active() { return active; } };
}
