/* CASES SECTION — with filters and case grid */

const DEFAULT_CASES = [
  {
    id: 'c1', tag: 'FINTECH', title: 'Helio — банковский core',
    summary: 'Core‑банкинг и онбординг для необанка, 1.2M пользователей.',
    year: 2025, scope: 'Платформа · API · Mobile',
    color: '#4db8ff', category: 'Платформы', size: 'sz-a', metrics: ['+38% retention', '120ms p95'],
  },
  {
    id: 'c2', tag: 'AI / SAAS', title: 'Nimbus Copilot',
    summary: 'AI‑ассистент для inside‑sales команд: автозаметки и follow‑up.',
    year: 2025, scope: 'LLM · UX · Backend',
    color: '#b388ff', category: 'AI', size: 'sz-b', metrics: ['×4.1 lead handling'],
  },
  {
    id: 'c3', tag: 'INDUSTRIAL', title: 'Forge Telemetry',
    summary: 'Real‑time телеметрия для металлургического холдинга.',
    year: 2024, scope: 'IoT · BI · DevOps',
    color: '#00e5ff', category: 'Платформы', size: 'sz-c',  metrics: ['12 ТБ/день'],
  },
  {
    id: 'c4', tag: 'E‑COMMERCE', title: 'Cordis Marketplace',
    summary: 'Маркетплейс премиальных брендов: каталог, аукционы, логистика.',
    year: 2024, scope: 'Web · iOS · Android',
    color: '#ffa14d', category: 'Продукты', size: 'sz-d', metrics: ['GMV ×2.6'],
  },
  {
    id: 'c5', tag: 'MEDIA', title: 'Polar — стриминг',
    summary: 'Подкаст‑платформа: рекомендации, лайв‑сессии, монетизация.',
    year: 2024, scope: 'Mobile · ML · Player',
    color: '#46f3a8', category: 'Продукты', size: 'sz-e', metrics: ['8.4 мин·сессия'],
  },
  {
    id: 'c6', tag: 'HEALTHTECH', title: 'Vita Records',
    summary: 'Электронные мед.карты + AI‑транскрибация приёмов.',
    year: 2023, scope: 'Web · Speech · HIPAA',
    color: '#4db8ff', category: 'AI', size: 'sz-f', metrics: ['‑44% бумажной работы'],
  },
  {
    id: 'c7', tag: 'CRYPTO', title: 'Obsidian Wallet',
    summary: 'Кошелёк с поддержкой L2, hardware и multi‑sig.',
    year: 2023, scope: 'Mobile · Cryptography',
    color: '#b388ff', category: 'Продукты', size: 'sz-g', metrics: ['Audit by Trail of Bits'],
  },
];

const CATEGORIES = ['Все', 'Платформы', 'Приложения', 'Веб', 'Боты', 'AI', 'Медиа'];

async function apiListCases() {
  const r = await fetch('/api/cases', { credentials: 'same-origin' });
  if (!r.ok) throw new Error('cases-fetch-failed');
  const j = await r.json();
  return j.cases || [];
}

async function apiUpsertCase(c) {
  const r = await fetch('/api/cases/' + encodeURIComponent(c.id), {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(c),
  });
  if (!r.ok) throw new Error('upsert-failed');
}

async function apiCreateCase(c) {
  const r = await fetch('/api/cases', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(c),
  });
  if (!r.ok) throw new Error('create-failed');
}

async function apiDeleteCase(id) {
  const r = await fetch('/api/cases/' + encodeURIComponent(id), {
    method: 'DELETE',
    credentials: 'same-origin',
  });
  if (!r.ok) throw new Error('delete-failed');
}

async function apiReorderCases(ids) {
  const r = await fetch('/api/cases/reorder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify({ ids }),
  });
  if (!r.ok) throw new Error('reorder-failed');
}

async function apiResetCases() {
  const r = await fetch('/api/cases/reset', {
    method: 'POST',
    credentials: 'same-origin',
  });
  if (!r.ok) throw new Error('reset-failed');
  const j = await r.json();
  return j.cases || [];
}

function notifyCasesUpdated() {
  window.dispatchEvent(new CustomEvent('scutoid:cases-updated'));
}

function CaseVisual({ color, seed }) {
  // procedural geometric wireframe inspired by scutoid faceting
  const lines = [];
  const pts = [];
  const rand = (i) => {
    const x = Math.sin((seed||0)*37 + i*7.3) * 10000;
    return x - Math.floor(x);
  };
  for (let i = 0; i < 14; i++) {
    pts.push({ x: 20 + rand(i) * 260, y: 20 + rand(i+30) * 140 });
  }
  for (let i = 0; i < pts.length; i++) {
    for (let j = i+1; j < pts.length; j++) {
      const dx = pts[i].x - pts[j].x;
      const dy = pts[i].y - pts[j].y;
      if (Math.hypot(dx, dy) < 75) {
        lines.push([pts[i], pts[j]]);
      }
    }
  }
  return (
    <svg viewBox="0 0 300 180" preserveAspectRatio="xMidYMid slice">
      <defs>
        <radialGradient id={`bgg-${seed}`} cx="50%" cy="0%" r="80%">
          <stop offset="0%" stopColor={color} stopOpacity="0.35"/>
          <stop offset="100%" stopColor={color} stopOpacity="0"/>
        </radialGradient>
      </defs>
      <rect width="300" height="180" fill="#0a0e18"/>
      <rect width="300" height="180" fill={`url(#bgg-${seed})`}/>
      {lines.map((l, i) => (
        <line key={i} x1={l[0].x} y1={l[0].y} x2={l[1].x} y2={l[1].y}
          stroke={color} strokeOpacity={0.5} strokeWidth="0.6"/>
      ))}
      {pts.map((p, i) => (
        <circle key={i} cx={p.x} cy={p.y} r="1.6" fill={color} />
      ))}
    </svg>
  );
}

function CaseCard({ c, idx, onOpen }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      el.style.setProperty('--lx', ((e.clientX - r.left)/r.width*100) + '%');
      el.style.setProperty('--ly', ((e.clientY - r.top)/r.height*100) + '%');
    };
    el.addEventListener('mousemove', onMove);
    return () => el.removeEventListener('mousemove', onMove);
  }, []);
  const cover = (c.images && c.images[0]) ? c.images[0].url : null;
  const hasImages = c.images && c.images.length > 0;
  return (
    <article
      ref={ref}
      className={"case " + c.size + (hasImages ? " has-images" : "")}
      onClick={hasImages ? onOpen : undefined}
      style={hasImages ? { cursor: 'pointer' } : undefined}
      role={hasImages ? 'button' : undefined}
      tabIndex={hasImages ? 0 : undefined}
      onKeyDown={hasImages ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(); } } : undefined}
    >
      {cover ? (
        <div className="case-bg" style={{ backgroundImage: `url("${cover}")` }}/>
      ) : (
        <div className="case-visual"><CaseVisual color={c.color} seed={idx+1}/></div>
      )}
      <div className="shimmer"></div>
      <div className="case-corner">
        <span style={{width:6, height:6, borderRadius:'50%', background: c.color, boxShadow:`0 0 8px ${c.color}`}}></span>
        {c.year}
        {hasImages && (
          <span style={{marginLeft: 6, opacity: .8}}>· {c.images.length} 📷</span>
        )}
      </div>
      <div className="case-content">
        <div className="case-tag">{c.tag}</div>
        <div className="case-title">{c.title}</div>
        {c.summary && <div className="case-summary">{c.summary}</div>}
        <div className="case-meta">
          <span>{c.scope}</span>
          {(c.metrics||[]).map((m,i) => (
            <span key={i} style={{color: c.color}}>• {m}</span>
          ))}
        </div>
      </div>
    </article>
  );
}

const ZOOM_MIN = 1;
const ZOOM_MAX = 4;
const ZOOM_STEP = 2.5; // double-click / button target scale

function CaseLightbox({ caseItem, onClose }) {
  const images = caseItem.images || [];
  const [idx, setIdx] = React.useState(0);
  const total = images.length;
  const next = React.useCallback(() => setIdx(i => (i + 1) % total), [total]);
  const prev = React.useCallback(() => setIdx(i => (i - 1 + total) % total), [total]);

  const stageRef = React.useRef(null);
  const imgRef = React.useRef(null);
  // Live transform kept in a ref (mutated during gestures, written straight to
  // the DOM) so panning/pinching stay 60fps without React re-renders. `zoomed`
  // state only flips cursor styling and gates swipe-vs-pan.
  const tf = React.useRef({ scale: 1, x: 0, y: 0 });
  const [zoomed, setZoomed] = React.useState(false);

  const apply = React.useCallback(() => {
    const el = imgRef.current;
    if (el) el.style.transform = `translate3d(${tf.current.x}px, ${tf.current.y}px, 0) scale(${tf.current.scale})`;
  }, []);

  // Keep the image from being dragged off into empty space.
  const clamp = React.useCallback(() => {
    const el = imgRef.current, stage = stageRef.current;
    if (!el || !stage) return;
    const sw = stage.clientWidth, sh = stage.clientHeight;
    const w = el.offsetWidth * tf.current.scale;
    const h = el.offsetHeight * tf.current.scale;
    const maxX = Math.max(0, (w - sw) / 2);
    const maxY = Math.max(0, (h - sh) / 2);
    tf.current.x = Math.min(maxX, Math.max(-maxX, tf.current.x));
    tf.current.y = Math.min(maxY, Math.max(-maxY, tf.current.y));
  }, []);

  const reset = React.useCallback(() => {
    tf.current = { scale: 1, x: 0, y: 0 };
    apply();
    setZoomed(false);
  }, [apply]);

  // Reset zoom whenever the visible image changes.
  React.useEffect(() => { reset(); }, [idx, reset]);

  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') onClose();
      else if (e.key === 'ArrowRight') next();
      else if (e.key === 'ArrowLeft') prev();
    };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [next, prev, onClose]);

  // All zoom/pan/swipe gestures use native non-passive listeners so we can
  // preventDefault on wheel + touchmove (React binds those passively, which
  // would otherwise let the page scroll/zoom under us).
  React.useEffect(() => {
    const stage = stageRef.current;
    if (!stage) return;

    const center = () => {
      const r = stage.getBoundingClientRect();
      return { ox: r.left + r.width / 2, oy: r.top + r.height / 2 };
    };
    // Zoom toward an anchor point (px,py given relative to stage center),
    // keeping that point visually fixed.
    const zoomTo = (scale, px, py) => {
      const prevS = tf.current.scale;
      scale = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, scale));
      const ratio = scale / prevS;
      tf.current.x = px * (1 - ratio) + tf.current.x * ratio;
      tf.current.y = py * (1 - ratio) + tf.current.y * ratio;
      tf.current.scale = scale;
      if (scale === ZOOM_MIN) { tf.current.x = 0; tf.current.y = 0; }
      clamp(); apply();
      setZoomed(scale > ZOOM_MIN);
    };

    const onWheel = (e) => {
      e.preventDefault();
      const { ox, oy } = center();
      zoomTo(tf.current.scale * (1 - e.deltaY * 0.0015), e.clientX - ox, e.clientY - oy);
    };

    const onDblClick = (e) => {
      const { ox, oy } = center();
      if (tf.current.scale > ZOOM_MIN) reset();
      else zoomTo(ZOOM_STEP, e.clientX - ox, e.clientY - oy);
    };

    // --- mouse pan (desktop) ---
    let drag = null;
    const onMouseDown = (e) => {
      if (tf.current.scale <= ZOOM_MIN) return;
      e.preventDefault();
      drag = { x: e.clientX, y: e.clientY, ox: tf.current.x, oy: tf.current.y };
    };
    const onMouseMove = (e) => {
      if (!drag) return;
      tf.current.x = drag.ox + (e.clientX - drag.x);
      tf.current.y = drag.oy + (e.clientY - drag.y);
      clamp(); apply();
    };
    const onMouseUp = () => { drag = null; };

    // --- touch: pinch / pan / swipe ---
    const t = { mode: null, sx: 0, sy: 0, ox: 0, oy: 0, dist: 0, scale: 1 };
    const dist = (touches) => Math.hypot(
      touches[0].clientX - touches[1].clientX,
      touches[0].clientY - touches[1].clientY,
    );
    const onTouchStart = (e) => {
      if (e.touches.length === 2) {
        t.mode = 'pinch'; t.dist = dist(e.touches) || 1; t.scale = tf.current.scale;
      } else if (e.touches.length === 1) {
        const p = e.touches[0];
        t.mode = tf.current.scale > ZOOM_MIN ? 'pan' : 'swipe';
        t.sx = p.clientX; t.sy = p.clientY; t.ox = tf.current.x; t.oy = tf.current.y;
      }
    };
    const onTouchMove = (e) => {
      if (t.mode === 'pinch' && e.touches.length === 2) {
        e.preventDefault();
        const { ox, oy } = center();
        const mx = (e.touches[0].clientX + e.touches[1].clientX) / 2 - ox;
        const my = (e.touches[0].clientY + e.touches[1].clientY) / 2 - oy;
        zoomTo(t.scale * (dist(e.touches) / t.dist), mx, my);
      } else if (t.mode === 'pan' && e.touches.length === 1) {
        e.preventDefault();
        const p = e.touches[0];
        tf.current.x = t.ox + (p.clientX - t.sx);
        tf.current.y = t.oy + (p.clientY - t.sy);
        clamp(); apply();
      }
    };
    const onTouchEnd = (e) => {
      if (t.mode === 'swipe' && total > 1) {
        const p = e.changedTouches[0];
        const dx = p.clientX - t.sx, dy = p.clientY - t.sy;
        if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) { if (dx < 0) next(); else prev(); }
      }
      t.mode = null;
    };

    stage.addEventListener('wheel', onWheel, { passive: false });
    stage.addEventListener('dblclick', onDblClick);
    stage.addEventListener('mousedown', onMouseDown);
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);
    stage.addEventListener('touchstart', onTouchStart, { passive: false });
    stage.addEventListener('touchmove', onTouchMove, { passive: false });
    stage.addEventListener('touchend', onTouchEnd);
    return () => {
      stage.removeEventListener('wheel', onWheel);
      stage.removeEventListener('dblclick', onDblClick);
      stage.removeEventListener('mousedown', onMouseDown);
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);
      stage.removeEventListener('touchstart', onTouchStart);
      stage.removeEventListener('touchmove', onTouchMove);
      stage.removeEventListener('touchend', onTouchEnd);
    };
  }, [next, prev, total, apply, clamp, reset]);

  const toggleZoom = () => {
    if (tf.current.scale > ZOOM_MIN) {
      reset();
    } else {
      tf.current.scale = ZOOM_STEP;
      clamp(); apply();
      setZoomed(true);
    }
  };

  if (!total) return null;
  const img = images[idx];
  const lightbox = (
    <div className="lightbox" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="lightbox-head">
        <div className="lightbox-meta">
          <div className="lightbox-tag" style={{color: caseItem.color}}>{caseItem.tag}</div>
          <div className="lightbox-title">{caseItem.title}</div>
        </div>
        <div className="lightbox-actions">
          <span className="lightbox-counter">{idx + 1} / {total}</span>
          <button className="lightbox-close" onClick={toggleZoom} aria-label={zoomed ? 'Уменьшить' : 'Увеличить'}>
            {zoomed ? (
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3M8 11h6"/>
              </svg>
            ) : (
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3M11 8v6M8 11h6"/>
              </svg>
            )}
          </button>
          <button className="lightbox-close" onClick={onClose} aria-label="Закрыть">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
              <path d="M6 6l12 12M18 6L6 18"/>
            </svg>
          </button>
        </div>
      </div>

      <div className={"lightbox-stage" + (zoomed ? ' zoomed' : '')} ref={stageRef}>
        {total > 1 && (
          <button className="lightbox-nav prev" onClick={prev} aria-label="Предыдущее">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M15 6l-6 6 6 6"/>
            </svg>
          </button>
        )}
        <img
          ref={imgRef}
          key={img.id}
          src={img.url}
          alt={`${caseItem.title} — изображение ${idx + 1} из ${total}`}
          className="lightbox-image"
          decoding="async"
          loading="eager"
          draggable="false"
        />
        {total > 1 && (
          <button className="lightbox-nav next" onClick={next} aria-label="Следующее">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M9 6l6 6-6 6"/>
            </svg>
          </button>
        )}
      </div>

      {total > 1 && (
        <div className="lightbox-thumbs">
          {images.map((im, i) => (
            <button key={im.id}
              className={"lightbox-thumb " + (i === idx ? 'active' : '')}
              onClick={() => setIdx(i)}
              style={{backgroundImage: `url("${im.url}")`}}
              aria-label={`Фото ${i + 1}`}
            />
          ))}
        </div>
      )}
    </div>
  );
  // Portal to body so the section's `transform` (scroll-reveal) doesn't trap our position:fixed.
  return ReactDOM.createPortal(lightbox, document.body);
}

function Cases() {
  const [cases, setCases] = React.useState(DEFAULT_CASES);
  const [filter, setFilter] = React.useState('Все');
  const [openCaseId, setOpenCaseId] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;
    const load = () => apiListCases()
      .then(list => { if (!cancelled && list.length) setCases(list); })
      .catch(() => { /* keep DEFAULT_CASES fallback */ });
    load();
    const onUpdate = () => load();
    window.addEventListener('scutoid:cases-updated', onUpdate);
    return () => {
      cancelled = true;
      window.removeEventListener('scutoid:cases-updated', onUpdate);
    };
  }, []);

  const filtered = filter === 'Все' ? cases : cases.filter(c => c.category === filter);
  const openCase = openCaseId ? cases.find(c => c.id === openCaseId) : null;

  return (
    <section className="section container" id="cases">
      <div className="section-head">
        <h2>Кейсы. <span className="italic">Технологии, которые работают.</span></h2>
        <div className="num">02 / РАБОТЫ</div>
      </div>

      <div className="cases-filters">
        {CATEGORIES.map(cat => (
          <button key={cat} className={"chip " + (filter === cat ? "active" : "")}
            onClick={() => setFilter(cat)}>
            {cat} <span style={{opacity:.5, marginLeft:6}}>
              {cat === 'Все' ? cases.length : cases.filter(c => c.category === cat).length}
            </span>
          </button>
        ))}
      </div>

      <div className="cases-grid">
        {filtered.map((c, i) => (
          <CaseCard key={c.id} c={c} idx={i} onOpen={() => setOpenCaseId(c.id)}/>
        ))}
      </div>

      {openCase && (openCase.images || []).length > 0 && (
        <CaseLightbox caseItem={openCase} onClose={() => setOpenCaseId(null)}/>
      )}
    </section>
  );
}

window.Cases = Cases;
window.apiListCases = apiListCases;
window.apiUpsertCase = apiUpsertCase;
window.apiCreateCase = apiCreateCase;
window.apiDeleteCase = apiDeleteCase;
window.apiReorderCases = apiReorderCases;
window.apiResetCases = apiResetCases;
window.notifyCasesUpdated = notifyCasesUpdated;
window.DEFAULT_CASES = DEFAULT_CASES;
window.CATEGORIES = CATEGORIES;
