/* ============================================================
   PORTFOLIO — "Aus der Praxis"
   Interactive circular project presentation for Ottoman Projekts.

   Architecture (production component map):
     PortfolioSection      — section shell, state, layout switch
     PortfolioFilters       — trade filter chips
     CircularProjectOrbit   — trig-positioned orbit, scroll/drag motion
     ProjectCard            — upright project tile
     LogoSegmentRing        — segmented "O" ring (SVG)
     RomanHeadScene         — marble bust, mouse-follow (image fallback +
                              dormant Three.js/GLB path, see MODEL_AVAILABLE)
     ProjectDetailModal     — fullscreen case-study overlay

   Motion uses rAF + lerp (orbit, head) and CSS transitions (filter
   redistribution, hover) — the framer-motion / R3F equivalents that run
   natively in this in-browser React setup. Drop a real GLB at
   /models/roman-head.glb and flip MODEL_AVAILABLE to activate WebGL.
   ============================================================ */

const PORTFOLIO = [
  { cat: 'entkernung',  label: 'Entkernung',   loc: 'Düsseldorf · Pempelfort', title: 'Altbau — Vollentkernung 1.240 m²',     img: 'assets/projects/entkernung.jpeg',     ratio: 0.80,
    blurb: 'Kontrollierter Rückbau bis auf den Rohbau. Schadstoffsanierung, Statiksicherung und besenreine Übergabe — alles aus einer Hand, dokumentiert vom ersten Schnitt an.' },
  { cat: 'statik',      label: 'Statik',        loc: 'Mönchengladbach',         title: 'Stahlträger-Einzug & Wanddurchbruch DG', img: 'assets/projects/statik.jpeg',      ratio: 0.72,
    blurb: 'Tragwerksplanung und Ausführung im Bestand. Lastabtragung neu gedacht — millimetergenauer Einzug von Stahlträgern für eine offene Dachgeschoss-Struktur.' },
  { cat: 'trockenbau',  label: 'Trockenbau',    loc: 'Köln',                    title: 'Bürofläche 480 m² — neue Raumstruktur', img: 'assets/projects/trockenbau.jpeg',  ratio: 1.0,
    blurb: 'Wie ein Bildhauer formen wir Räume: Trennwände, Akustikdecken und Brandschutz präzise gesetzt — von der Unterkonstruktion bis zur fertigen Oberfläche.' },
  { cat: 'innenausbau', label: 'Innenausbau',   loc: 'Neuss',                   title: 'Maisonette — Komplettausbau schlüsselfertig', img: 'assets/projects/innenausbau.jpeg',  ratio: 0.88,
    blurb: 'Schlüsselfertiger Innenausbau mit einem Ansprechpartner. Gewerke koordiniert, Termine gehalten, Qualität bis ins Detail — bezugsfertig übergeben.' },
  { cat: 'aussengeruest', label: 'Außengerüst', loc: 'Düsseldorf', title: 'Fassadengerüst — 22 m Höhe',         img: 'assets/projects/aussengeruest.jpeg',   ratio: 0.70,
    blurb: 'Standsicheres Fassaden- und Arbeitsgerüst nach DIN. Auf- und Abbau termingerecht koordiniert mit Sanierung und Malerarbeiten — sicher auf jeder Höhe.' },
  { cat: 'abriss',      label: 'Abriss',        loc: 'Krefeld',                 title: 'Gewerbehalle 1.800 m² — Komplettrückbau', img: 'assets/projects/abriss.png', ratio: 1.18,
    blurb: 'Komplettrückbau einer Gewerbehalle inkl. Fundament. Sortenreine Trennung, fachgerechte Entsorgung und ein geräumtes, übergabefertiges Grundstück.' },
  { cat: 'malerarbeiten', label: 'Malerarbeiten', loc: 'Düsseldorf',            title: 'Innenanstrich & Fassade — Premium-Finish', img: 'assets/projects/malerarbeiten.jpeg', ratio: 0.84,
    blurb: 'Der letzte Schliff, der alles zusammenbringt. Innenanstrich, Lasur und Fassade — sauber kantengezogen, mit Materialberatung und langlebigem Ergebnis.' },
  { cat: 'bodenarbeiten', label: 'Bodenarbeiten', loc: 'Köln',                  title: 'Estrich, Parkett & Fliesen — 320 m²',  img: 'assets/projects/bodenarbeiten.jpeg',   ratio: 1.0,
    blurb: 'Von der Untergrundvorbereitung bis zur fertigen Oberfläche. Estrich, Parkett, Vinyl und Fliesen — eben, belastbar und passend zum Objekt verlegt.' },
];

const PF_FILTERS = [
  ['all', 'Alle'], ['entkernung', 'Entkernung'], ['statik', 'Statik'],
  ['trockenbau', 'Trockenbau'], ['innenausbau', 'Innenausbau'],
  ['aussengeruest', 'Außengerüst'], ['abriss', 'Abriss'],
];

/* portfolio trade -> contact-form "Projektart" option value */
const SERVICE_MAP = {
  entkernung:   'Abriss & Entkernung',
  abriss:       'Abriss & Entkernung',
  statik:       'Statik',
  trockenbau:   'Trockenbau',
  innenausbau:  'Innenausbau',
  aussengeruest:'Außengerüst',
  malerarbeiten:'Malerarbeiten',
  bodenarbeiten:'Bodenarbeiten',
};

/* Preselect a trade in the contact form, then glide to it. */
function requestService(cat) {
  const val = SERVICE_MAP[cat] || '';
  window.__ottomanService = val;
  window.dispatchEvent(new CustomEvent('ottoman:service', { detail: val }));
  setTimeout(() => {
    const el = document.getElementById('contact');
    if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 72, behavior: 'smooth' });
  }, 90);
}

/* WebGL is opt-in: drop a real model at /models/roman-head.glb and set true.
   Until then the high-detail marble bust image (with 3D mouse-tilt) is shown,
   keeping the preview clean and dependency-free. */
const MODEL_AVAILABLE = false;
const ROMAN_MODEL_URL = '/models/roman-head.glb';

const prefersReduced = () =>
  window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

/* ---------- LogoSegmentRing ------------------------------------ */
function polar(cx, cy, r, deg) {
  const a = (deg - 90) * Math.PI / 180;
  return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
}
function arcPath(cx, cy, r, start, end) {
  const [x0, y0] = polar(cx, cy, r, start);
  const [x1, y1] = polar(cx, cy, r, end);
  const large = (end - start) % 360 > 180 ? 1 : 0;
  return `M ${x0.toFixed(2)} ${y0.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${x1.toFixed(2)} ${y1.toFixed(2)}`;
}
function LogoSegmentRing({ size = 300 }) {
  const s = size, c = s / 2, sw = s * 0.072, r = c - sw / 2 - 1;
  // 4 equal segments (78° each) with equal 12° gaps at N/E/S/W; orange upper-right.
  const segs = [
    { from: 6,   to: 84,  cls: 'pf-ring-orange' }, // upper-right accent
    { from: 96,  to: 174, cls: 'pf-ring-ivory'  },
    { from: 186, to: 264, cls: 'pf-ring-ivory'  },
    { from: 276, to: 354, cls: 'pf-ring-ivory'  },
  ];
  return (
    <svg className="pf-ring" width={s} height={s} viewBox={`0 0 ${s} ${s}`} aria-hidden="true">
      {segs.map((seg, i) => (
        <path key={i} className={seg.cls} d={arcPath(c, c, r, seg.from, seg.to)}
          fill="none" strokeWidth={sw} strokeLinecap="butt" />
      ))}
    </svg>
  );
}

/* ---------- RomanHeadScene -------------------------------------
   Renders the slot; the transparent R3F <Canvas> (head-gl.module.js)
   mounts into .pf-head-gl and loads models/roman-head.glb. */
function RomanHeadScene() {
  return (
    <div className="pf-head">
      <div className="pf-head-glow" aria-hidden="true"></div>
      <div className="pf-head-gl" aria-hidden="true"></div>
    </div>
  );
}

/* ---------- ProjectCard ---------------------------------------- */
const ProjectCard = React.forwardRef(function ProjectCard({ item, index, onOpen, mode }, ref) {
  return (
    <article
      ref={ref}
      className={'pf-card' + (mode === 'carousel' ? ' is-carousel' : '')}
      style={{ '--ratio': item.ratio }}
      role="button" tabIndex={0}
      aria-label={`${item.label} — ${item.title}`}
      onClick={() => onOpen(item)}
      onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(item); } }}
    >
      <div className="pf-card-media">
        <img src={item.img} alt={item.label} loading="lazy" draggable="false" />
        <span className="pf-card-veil" aria-hidden="true"></span>
      </div>
      <span className="pf-card-ix" aria-hidden="true">{String(index + 1).padStart(2, '0')}</span>
      <div className="pf-card-foot">
        <span className="pf-card-cat">{item.label}</span>
        <span className="pf-card-title">{item.loc}</span>
      </div>
    </article>
  );
});

/* ---------- CircularProjectOrbit ------------------------------- */
function CircularProjectOrbit({ items, activeIds, onOpen, reduce, pointer }) {
  const stageRef = React.useRef(null);
  const cardRefs = React.useRef([]);
  const angle = React.useRef(items.map((_, i) => 0)); // per-card current angle
  const inited = React.useRef(false);
  const rot = React.useRef(0);        // displayed base rotation
  const acc = React.useRef(0);        // target base rotation
  const drag = React.useRef({ on: false, lastA: 0 });
  const hovering = React.useRef(false);
  const prevScroll = React.useRef(null);
  const dims = React.useRef({ S: 900, R: 330, cw: 150 });

  // Stage sizing
  React.useEffect(() => {
    const el = stageRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => {
      const w = el.clientWidth;
      const S = w;
      dims.current = { S, R: S * 0.378, cw: Math.max(118, S * 0.137) };
      el.style.setProperty('--cw', dims.current.cw + 'px');
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // rAF positioning loop
  React.useEffect(() => {
    let raf;
    const ROT_RANGE = 0.86; // rad across the scroll of the section
    const loop = () => {
      const { S, R } = dims.current;
      const cx = S / 2, cy = S / 2;
      // scroll-driven rotation (accumulated so hover/drag never jumps)
      const sec = document.getElementById('projects');
      if (sec && !reduce) {
        const rect = sec.getBoundingClientRect();
        const vh = window.innerHeight || 800;
        const p = clamp((vh - rect.top) / (vh + rect.height), 0, 1);
        const target = (p - 0.5) * ROT_RANGE;
        if (prevScroll.current === null) prevScroll.current = target;
        const d = target - prevScroll.current;
        if (!hovering.current && !drag.current.on) acc.current += d;
        prevScroll.current = target;
      }
      rot.current += (acc.current - rot.current) * 0.08;

      const actives = items.map((it, i) => activeIds.has(i) ? i : -1).filter((i) => i >= 0);
      const n = Math.max(1, actives.length);
      items.forEach((it, i) => {
        const el = cardRefs.current[i];
        if (!el) return;
        const active = activeIds.has(i);
        if (active) {
          const slot = actives.indexOf(i);
          const targetA = rot.current + slot * (Math.PI * 2 / n) - Math.PI / 2;
          if (!inited.current) angle.current[i] = targetA;
          // shortest-path lerp
          let da = targetA - angle.current[i];
          da = Math.atan2(Math.sin(da), Math.cos(da));
          angle.current[i] += da * 0.12;
          const a = angle.current[i];
          const x = cx + R * Math.cos(a) - el.offsetWidth / 2;
          const y = cy + R * Math.sin(a) - el.offsetHeight / 2;
          const depth = (Math.sin(a) + 1) / 2;            // 0 back(top) .. 1 front(bottom)
          const sc = 0.9 + depth * 0.12;
          const op = 0.74 + depth * 0.26;
          el.style.transform = `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) scale(${sc.toFixed(3)})`;
          el.style.opacity = op.toFixed(3);
          el.style.zIndex = String(10 + Math.round(depth * 20));
          el.style.pointerEvents = 'auto';
        } else {
          // exit: fade + shrink toward last position
          const a = angle.current[i];
          const x = cx + R * Math.cos(a) - el.offsetWidth / 2;
          const y = cy + R * Math.sin(a) - el.offsetHeight / 2;
          el.style.transform = `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) scale(0.6)`;
          el.style.opacity = '0';
          el.style.pointerEvents = 'none';
          el.style.zIndex = '1';
        }
      });
      inited.current = true;
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [items, activeIds, reduce]);

  // Drag to rotate
  React.useEffect(() => {
    const el = stageRef.current;
    if (!el || reduce) return;
    const angleAt = (e) => {
      const r = el.getBoundingClientRect();
      return Math.atan2(e.clientY - (r.top + r.height / 2), e.clientX - (r.left + r.width / 2));
    };
    const down = (e) => {
      if (e.target.closest('.pf-card') || e.target.closest('.pf-center')) return;
      drag.current.on = true; drag.current.lastA = angleAt(e);
      el.classList.add('is-grabbing'); el.setPointerCapture && el.setPointerCapture(e.pointerId);
    };
    const move = (e) => {
      if (!drag.current.on) return;
      const a = angleAt(e);
      let d = a - drag.current.lastA;
      d = Math.atan2(Math.sin(d), Math.cos(d));
      acc.current += d; drag.current.lastA = a;
    };
    const up = () => { drag.current.on = false; el.classList.remove('is-grabbing'); };
    el.addEventListener('pointerdown', down);
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    return () => { el.removeEventListener('pointerdown', down); window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); };
  }, [reduce]);

  return (
    <div className="pf-stage" ref={stageRef}
      onMouseEnter={() => {}}
      onPointerOver={(e) => { if (e.target.closest('.pf-card')) hovering.current = true; }}
      onPointerOut={(e) => { if (e.target.closest('.pf-card')) hovering.current = false; }}
    >
      <div className="pf-center">
        <LogoSegmentRing size={300} />
        <RomanHeadScene pointer={pointer} reduce={reduce} />
      </div>
      {items.map((it, i) => (
        <ProjectCard key={it.cat} item={it} index={i} mode="orbit"
          ref={(n) => { cardRefs.current[i] = n; }} onOpen={onOpen} />
      ))}
      <span className="pf-orbit-ring" aria-hidden="true"></span>
    </div>
  );
}

function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }

/* ---------- Carousel (mobile) ---------------------------------- */
function ProjectCarousel({ items, activeIds, onOpen, pointer, reduce }) {
  const shown = items.filter((_, i) => activeIds.has(i));
  return (
    <div className="pf-mobile">
      <div className="pf-center pf-center-static">
        <LogoSegmentRing size={230} />
        <RomanHeadScene pointer={pointer} reduce={reduce} />
      </div>
      <div className="pf-track" role="list">
        {shown.map((it) => {
          const gi = items.indexOf(it);
          return (
            <ProjectCard key={it.cat} item={it} index={gi} mode="carousel" onOpen={onOpen} />
          );
        })}
      </div>
    </div>
  );
}

/* ---------- ProjectDetailModal --------------------------------- */
function ProjectDetailModal({ item, onClose }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = prev; };
  }, [onClose]);
  if (!item) return null;
  return (
    <div className="pf-modal" role="dialog" aria-modal="true" aria-label={item.title} onClick={onClose}>
      <div className="pf-modal-card" onClick={(e) => e.stopPropagation()}>
        <button className="pf-modal-x" onClick={onClose} aria-label="Schließen">
          <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 3l10 10M13 3L3 13" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
        </button>
        <div className="pf-modal-media"><img src={item.img} alt={item.label} /></div>
        <div className="pf-modal-body">
          <span className="pf-modal-cat">{item.label}</span>
          <h3>{item.title}</h3>
          <span className="pf-modal-loc">{item.loc}</span>
          <p>{item.blurb}</p>
          <div className="pf-modal-foot">
            <a className="btn btn-primary" href="#contact"
              onClick={(e) => { e.preventDefault(); requestService(item.cat); onClose(); }}>
              <span className="dot"></span>Ähnliches Projekt anfragen</a>
            <span className="pf-modal-tag">Referenz · {item.label}</span>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- PortfolioFilters ----------------------------------- */
function PortfolioFilters({ active, onChange }) {
  return (
    <div className="projects-filters pf-filters" data-reveal role="tablist" aria-label="Projektfilter">
      {PF_FILTERS.map(([id, txt]) => (
        <button key={id} role="tab" aria-selected={active === id}
          className={'filter-chip' + (active === id ? ' is-active' : '')}
          onClick={() => onChange(id)}>{txt}</button>
      ))}
    </div>
  );
}

/* ---------- PortfolioSection ----------------------------------- */
function PortfolioSection() {
  const [filter, setFilter] = React.useState('all');
  const [open, setOpen] = React.useState(null);
  const [carousel, setCarousel] = React.useState(false);
  const reduce = React.useRef(prefersReduced()).current;
  const pointer = React.useRef({ x: 0, y: 0 });

  // global pointer (for head tracking) normalised to viewport (-1..1)
  React.useEffect(() => {
    if (reduce) return;
    const onMove = (e) => {
      pointer.current.x = (e.clientX / window.innerWidth) * 2 - 1;
      pointer.current.y = (e.clientY / window.innerHeight) * 2 - 1;
    };
    window.addEventListener('mousemove', onMove, { passive: true });
    return () => window.removeEventListener('mousemove', onMove);
  }, [reduce]);

  // layout mode
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 720px)');
    const sync = () => setCarousel(mq.matches);
    sync();
    mq.addEventListener ? mq.addEventListener('change', sync) : mq.addListener(sync);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', sync) : mq.removeListener(sync); };
  }, []);

  const activeIds = React.useMemo(() => {
    const s = new Set();
    PORTFOLIO.forEach((p, i) => { if (filter === 'all' || p.cat === filter) s.add(i); });
    return s;
  }, [filter]);

  return (
    <section id="projects" className="projects-section pf-section" data-screen-label="Projects">
      <div className="container">
        <SectionHead label="III · Projekte" deck="Ausgewählte Referenzen aus dem Großraum NRW. Echte Baustellen, echte Ergebnisse — kein Stockfoto-Material.">
          Aus der <em>Praxis.</em>
        </SectionHead>

        <PortfolioFilters active={filter} onChange={setFilter} />

        <div className="pf-arena" data-reveal>
          {carousel
            ? <ProjectCarousel items={PORTFOLIO} activeIds={activeIds} onOpen={setOpen} pointer={pointer} reduce={reduce} />
            : <CircularProjectOrbit items={PORTFOLIO} activeIds={activeIds} onOpen={setOpen} pointer={pointer} reduce={reduce} />}
          {!carousel && <p className="pf-hint" aria-hidden="true">Ziehen zum Drehen · Klicken für Details</p>}
        </div>
      </div>
      {open && <ProjectDetailModal item={open} onClose={() => setOpen(null)} />}
    </section>
  );
}

Object.assign(window, {
  PortfolioSection, PortfolioFilters, CircularProjectOrbit, ProjectCard,
  LogoSegmentRing, RomanHeadScene, ProjectDetailModal,
  // keep old name so app.jsx works either way
  Projects: PortfolioSection,
});
