/* ui.jsx — geteilte UI-Bausteine */
const { useEffect: useEffectUI, useRef: useRefUI, useState: useStateUI } = React;

/* ---- Kontaktformular-Versand ans CMS-Backend (Security-Standards §7: Honeypot
   + Server-Validation + Rate-Limit auf Backend-Seite, reCAPTCHA optional) ---- */
async function submitContact({ name, mail, msg, honeypot }) {
  try {
    const res = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, email: mail, message: msg, honeypot, source: window.location.href }),
    });
    const data = await res.json();
    if (!res.ok || !data.ok) return { ok: false, error: data.error || 'Senden fehlgeschlagen.' };
    return { ok: true };
  } catch (e) {
    return { ok: false, error: 'Verbindung fehlgeschlagen. Bitte versuche es später erneut.' };
  }
}

/* ---- Arrow icon ---- */
function Arrow({ s = 16 }) {
  return (
    <svg className="arrow" width={s} height={s} viewBox="0 0 24 24" fill="none">
      <path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" strokeWidth="2.2"
      strokeLinecap="round" strokeLinejoin="round" />
    </svg>);

}

/* ---- Logo (code-built so it adapts to theme) ---- */
function Logo({ onClick }) {
  return (
    <div className="logo" onClick={onClick} role="button" aria-label="Bevisible Start">
      <span className="logo-hash">#</span>
      <span className="logo-word">
        <span className="b">Bevisible</span>
        <span className="c">social media consulting</span>
      </span>
    </div>);

}

/* ---- Buttons ---- */
function Btn({ children, variant = 'primary', onClick, arrow = true }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      <span className="lbl">{children}{arrow && <Arrow />}</span>
    </button>);

}
function LinkArrow({ children, onClick }) {
  return (
    <button className="linkarrow" onClick={onClick}>
      <span className="ln" style={{ fontSize: "16px" }}>{children}</span>
      <Arrow s={15} />
    </button>);

}

/* ---- Reveal on scroll (rect-based + React-state driven so it survives
        re-renders, with a fail-open for environments whose transition
        clock is frozen, e.g. offscreen-rendered capture iframes) ---- */
const _revQ = [];
function _checkReveals() {
  const h = window.innerHeight || 800;
  for (let i = _revQ.length - 1; i >= 0; i--) {
    const en = _revQ[i];
    if (!en || !en.el || !en.el.isConnected) {_revQ.splice(i, 1);continue;}
    const r = en.el.getBoundingClientRect();
    if (r.top < h * 0.94 && r.bottom > -60) {
      en.show();
      const el = en.el;
      // fail-open: if the transition clock never advances, force final state
      setTimeout(() => {
        if (el.isConnected && parseFloat(getComputedStyle(el).opacity) < 0.85) {
          el.style.transition = 'none';el.style.opacity = '1';el.style.transform = 'none';
        }
      }, 1400);
      _revQ.splice(i, 1);
    }
  }
}
let _revBound = false;
function _bindReveals() {
  if (_revBound) return;_revBound = true;
  const run = () => requestAnimationFrame(_checkReveals);
  window.addEventListener('scroll', run, { passive: true });
  window.addEventListener('resize', run);
}
function Reveal({ children, delay = 0, as: Tag = 'div', className = '', style = {} }) {
  const ref = useRefUI(null);
  const [vis, setVis] = useStateUI(false);
  useEffectUI(() => {
    const el = ref.current;
    if (!el) return;
    const entry = { el, show: () => setVis(true) };
    _revQ.push(entry);
    _bindReveals();
    _checkReveals();
    const t1 = setTimeout(_checkReveals, 120);
    const t2 = setTimeout(_checkReveals, 420);
    return () => {clearTimeout(t1);clearTimeout(t2);const k = _revQ.indexOf(entry);if (k > -1) _revQ.splice(k, 1);};
  }, []);
  return (
    <Tag ref={ref} className={`reveal ${vis ? 'in' : ''} ${className}`} style={{ transitionDelay: delay + 'ms', ...style }}>
      {children}
    </Tag>);

}

/* ---- Marquee ---- */
function Marquee({ items }) {
  const row = [...items, ...items];
  return (
    <div className="marquee" aria-hidden="true">
      <div className="marquee__track">
        {row.map((it, i) =>
        <span key={i} className={it.dot ? 'dot' : it.grad ? 'grad-text' : ''}>{it.t}</span>
        )}
      </div>
    </div>);

}

/* ---- Media placeholder (drop zone look) ---- */
function MediaPlaceholder({ label = 'Bild', video = false, ratio = '4/3', style = {} }) {
  return (
    <div className="media-ph" style={{ aspectRatio: ratio, ...style }}>
      <div className="ph-inner">
        {video &&
        <div className="ph-play">
            <svg width="26" height="26" viewBox="0 0 24 24" fill="#1a1a1a"><path d="M8 5v14l11-7z" /></svg>
          </div>
        }
        <div className="ph-tag">{video ? '▢ Video folgt' : '▢ ' + label}</div>
      </div>
    </div>);

}

/* ---- Social icon chip ---- */
function Social({ label, href = '#' }) {
  const map = { X: 'X', LinkedIn: 'in', Instagram: 'IG', Facebook: 'f' };
  return <a href={href} title={label} onClick={(e) => e.preventDefault()}>{map[label] || label}</a>;
}

/* ---- NAV ---- */
const NAV_ITEMS = [
{ id: 'home', label: 'Start' },
{ id: 'projekte', label: 'Projekte' },
{ id: 'ueber', label: 'Über uns' },
{ id: 'schulungen', label: 'Schulungen' },
{ id: 'blog', label: 'Blog' },
{ id: 'portal', label: 'Kundenportal' }];


function Nav({ page, go }) {
  const [scrolled, setScrolled] = useStateUI(false);
  const [onCover, setOnCover] = useStateUI(page === 'home');
  const [open, setOpen] = useStateUI(false);
  useEffectUI(() => {
    const onS = () => {
      const y = window.scrollY;
      setScrolled(y > 24);
      setOnCover(page === 'home' && y < window.innerHeight * 0.66);
    };
    onS();
    window.addEventListener('scroll', onS, { passive: true });
    window.addEventListener('resize', onS);
    return () => {window.removeEventListener('scroll', onS);window.removeEventListener('resize', onS);};
  }, [page]);
  const nav = (id) => {go(id);setOpen(false);};
  return (
    <>
      <nav className={`nav ${scrolled && !onCover ? 'scrolled' : ''} ${onCover ? 'on-cover' : ''}`}>
        <Logo onClick={() => nav('home')} />
        <div className="nav-links">
          {NAV_ITEMS.map((it) =>
          <button key={it.id}
          className={`nav-link ${page === it.id ? 'active' : ''}`}
          onClick={() => nav(it.id)}>{it.label}</button>
          )}
          <span className="nav-cta">
            <Btn variant="primary" onClick={() => nav('kontakt')}>Anfragen</Btn>
          </span>
        </div>
        <button className="burger" onClick={() => setOpen((o) => !o)} aria-label="Menü">
          <span style={open ? { transform: 'translateY(7px) rotate(45deg)' } : {}}></span>
          <span style={open ? { opacity: 0 } : {}}></span>
          <span style={open ? { transform: 'translateY(-7px) rotate(-45deg)' } : {}}></span>
        </button>
      </nav>
      <div className={`mobile-menu ${open ? 'open' : ''}`}>
        {NAV_ITEMS.map((it) =>
        <button key={it.id} onClick={() => nav(it.id)}
        className={page === it.id ? 'grad-text' : ''}>{it.label}</button>
        )}
        <div style={{ marginTop: 28 }}>
          <Btn onClick={() => nav('kontakt')}>Projekt anfragen</Btn>
        </div>
      </div>
    </>);

}

/* ---- FOOTER ---- */
function Footer({ go }) {
  const megaRef = useRefUI(null);
  useEffectUI(() => {
    const el = megaRef.current; if (!el) return;
    el.classList.add('armed');
    const reveal = () => { el.classList.add('in'); cleanup(); };
    // rect-basierter Check (robuster als IO allein — feuert auch, wenn der
    // Footer schon beim Laden im Viewport ist oder IO nicht triggert)
    const check = () => {
      const r = el.getBoundingClientRect();
      if (r.top < (window.innerHeight || 800) * 0.9 && r.bottom > 0) reveal();
    };
    const onScroll = () => requestAnimationFrame(check);
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) reveal(); });
    }, { threshold: 0.2 });
    io.observe(el);
    check();
    const t1 = setTimeout(check, 200);
    // Fail-open: falls nichts triggert, nach 1,6s trotzdem einblenden
    const t2 = setTimeout(reveal, 1600);
    function cleanup() {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      io.disconnect(); clearTimeout(t1); clearTimeout(t2);
    }
    return cleanup;
  }, []);
  return (
    <footer className="footer">
      <div className="wrap">
        <nav className="foot-nav">
          {NAV_ITEMS.map((it) =>
          <a key={it.id} className="foot-nav-link" onClick={() => go(it.id)}>{it.label}</a>
          )}
        </nav>
      </div>
      <div className="foot-mega" ref={megaRef} aria-hidden="true">
        {['Be', 'Visible', 'With', 'Us'].map((w, i) =>
        <span className="fm-word" style={{ '--i': i }} key={i}>{w}</span>
        )}
      </div>
      <div className="wrap">
        <div className="footer-bottom">
          <span>© {new Date().getFullYear()} bevisible GmbH · Social Media Consulting</span>
          <span className="flex gap-m wrap-f">
            <a className="fl" style={{ display: 'inline', cursor: 'pointer' }} onClick={() => go('datenschutz')}>Datenschutz</a>
            <a className="fl" style={{ display: 'inline', cursor: 'pointer' }} onClick={() => go('impressum')}>Impressum</a>
          </span>
        </div>
      </div>
    </footer>);

}

/* ---- Social platform glyphs (currentColor) ---- */
function SocialGlyph({ name }) {
  switch (name) {
    case 'instagram':return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9">
        <rect x="3" y="3" width="18" height="18" rx="5.4" /><circle cx="12" cy="12" r="4.2" />
        <circle cx="17.4" cy="6.6" r="1.1" fill="currentColor" stroke="none" /></svg>);
    case 'tiktok':return (
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M16.5 3c.3 2.2 1.6 3.7 3.8 3.9v2.6c-1.3.1-2.5-.3-3.8-1v5.9c0 3.6-2.7 5.9-5.7 5.6-2.9-.3-4.8-2.9-4.2-5.8.5-2.4 2.7-3.9 5.2-3.6v2.8c-.4-.1-.8-.2-1.2-.1-1.1.1-1.9 1-1.8 2.1.1 1.1 1 1.9 2.1 1.8 1.1-.1 1.7-1 1.7-2.2V3h2.9z" /></svg>);
    case 'youtube':return (
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M21.6 7.2c-.2-1-.9-1.7-1.9-2C18 4.8 12 4.8 12 4.8s-6 0-7.7.4c-1 .3-1.7 1-1.9 2C2 8.9 2 12 2 12s0 3.1.4 4.8c.2 1 .9 1.7 1.9 2 1.7.4 7.7.4 7.7.4s6 0 7.7-.4c1-.3 1.7-1 1.9-2 .4-1.7.4-4.8.4-4.8s0-3.1-.4-4.8zM10 15.2V8.8l5.2 3.2-5.2 3.2z" /></svg>);
    case 'linkedin':return (
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.98 3.5a2.5 2.5 0 110 5 2.5 2.5 0 010-5zM3 9h4v12H3V9zm6 0h3.8v1.7h.05c.53-1 1.83-2.05 3.77-2.05 4.03 0 4.78 2.65 4.78 6.1V21H17.6v-5.4c0-1.3 0-2.95-1.8-2.95-1.8 0-2.07 1.4-2.07 2.85V21H9V9z" /></svg>);
    case 'facebook':return (
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M14 9V7c0-.9.6-1.1 1-1.1h2V2.5h-2.7C11.2 2.5 10 4.5 10 6.7V9H7.5v3.4H10V21.5h4V12.4h2.6l.4-3.4H14z" /></svg>);
    case 'x':return (
        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.5 3h3l-6.6 7.5L21.8 21h-6l-4.7-6.1L5.6 21H2.5l7-8L2.5 3h6.1l4.2 5.6L17.5 3zm-1.1 16h1.7L7.7 4.8H5.9L16.4 19z" /></svg>);
    default:return null;
  }
}

/* ---- Floating SoMe chips (bob + cursor parallax) ---- */
const FLOAT_ITEMS = [
{ n: 'instagram', s: { left: '3%', top: '14%' }, r: '-8deg', dur: '5.4s', depth: 26 },
{ n: 'tiktok', s: { right: '5%', top: '4%' }, r: '7deg', dur: '6.2s', depth: 38, opt: true },
{ n: 'youtube', s: { right: '1%', top: '44%' }, r: '-5deg', dur: '5.8s', depth: 18 },
{ n: 'linkedin', s: { left: '8%', bottom: '6%' }, r: '6deg', dur: '6.6s', depth: 30, opt: true },
{ n: 'facebook', s: { left: '0%', top: '50%' }, r: '-6deg', dur: '5.1s', depth: 20 },
{ n: 'x', s: { right: '12%', bottom: '2%' }, r: '9deg', dur: '6.9s', depth: 34, opt: true }];

function FloatingSocials() {
  const ref = useRefUI(null);
  useEffectUI(() => {
    const layer = ref.current;if (!layer) return;
    let raf,tx = 0,ty = 0,cx = 0,cy = 0;
    const onMove = (e) => {
      tx = (e.clientX / window.innerWidth - 0.5) * 2;
      ty = (e.clientY / window.innerHeight - 0.5) * 2;
    };
    const tick = () => {
      cx += (tx - cx) * 0.06;cy += (ty - cy) * 0.06;
      const kids = layer.children;
      for (let i = 0; i < kids.length; i++) {
        const d = +kids[i].dataset.depth || 20;
        kids[i].style.transform = `translate(${cx * d}px, ${cy * d}px)`;
      }
      raf = requestAnimationFrame(tick);
    };
    window.addEventListener('pointermove', onMove, { passive: true });
    raf = requestAnimationFrame(tick);
    return () => {cancelAnimationFrame(raf);window.removeEventListener('pointermove', onMove);};
  }, []);
  return (
    <div className="floats" ref={ref} aria-hidden="true">
      {FLOAT_ITEMS.map((it) =>
      <div key={it.n} className={`float-chip ${it.opt ? 'opt' : ''}`} data-depth={it.depth} style={it.s}>
          <div className="float-bob" style={{ '--dur': it.dur, '--r': it.r }}>
            <div className="somark"><SocialGlyph name={it.n} /></div>
          </div>
        </div>
      )}
    </div>);

}

/* ---- Client logo band (train effect, left → right) ---- */
const LOGO_PH = [
{ name: 'Nordwind', sub: 'Verband', mark: 'ring' },
{ name: 'Volta', sub: 'Energie', mark: 'bolt' },
{ name: 'Meridian', sub: 'KMU', mark: 'circle' },
{ name: 'Kraftwerk', sub: 'Politik', mark: 'square' },
{ name: 'Prisma', sub: 'Retail', mark: 'tri' },
{ name: 'Atlas', sub: 'Bau', mark: 'hash' },
{ name: 'Lumen', sub: 'Studio', mark: 'dot' },
{ name: 'Echo', sub: 'Kampagne', mark: 'slash' }];

function LogoMark({ type }) {
  const c = { width: 34, height: 34, display: 'block' };
  const common = { fill: 'none', stroke: 'currentColor', strokeWidth: 2.2 };
  switch (type) {
    case 'ring':return <svg style={c} viewBox="0 0 36 36"><circle cx="18" cy="18" r="13" {...common} /><circle cx="18" cy="18" r="5" {...common} /></svg>;
    case 'bolt':return <svg style={c} viewBox="0 0 36 36"><path d="M20 4 8 20h8l-2 12 12-16h-8l2-12z" fill="currentColor" /></svg>;
    case 'circle':return <svg style={c} viewBox="0 0 36 36"><circle cx="18" cy="18" r="13" fill="currentColor" /></svg>;
    case 'square':return <svg style={c} viewBox="0 0 36 36"><rect x="6" y="6" width="24" height="24" rx="4" {...common} /></svg>;
    case 'tri':return <svg style={c} viewBox="0 0 36 36"><path d="M18 5 31 30H5L18 5z" {...common} strokeLinejoin="round" /></svg>;
    case 'hash':return <svg style={c} viewBox="0 0 36 36"><text x="18" y="27" textAnchor="middle" fontSize="30" fontWeight="900" fill="currentColor">#</text></svg>;
    case 'dot':return <svg style={c} viewBox="0 0 36 36"><circle cx="12" cy="18" r="5" fill="currentColor" /><circle cx="26" cy="18" r="5" {...common} /></svg>;
    case 'slash':return <svg style={c} viewBox="0 0 36 36"><path d="M24 5 12 31" {...common} strokeWidth="4" strokeLinecap="round" /></svg>;
    default:return null;
  }
}
function LogoBand({ label = 'Kunden' }) {
  const [logos, setLogos] = useStateUI(null); // null = noch nicht geladen
  useEffectUI(() => {
    fetch('/api/logos')
      .then((r) => (r.ok ? r.json() : []))
      .then((data) => setLogos(Array.isArray(data) ? data : []))
      .catch(() => setLogos([]));
  }, []);

  // Fallback auf Platzhalter-SVGs, solange keine echten Firmenlogos erfasst sind
  if (!logos || logos.length === 0) {
    const row = [...LOGO_PH, ...LOGO_PH];
    return (
      <div className="logoband" aria-label="Kunden">
        <div className="logoband__track">
          {row.map((l, i) =>
          <div className="logo-ph" key={i}>
              <span className="lp-mark"><LogoMark type={l.mark} /></span>
              <span className="lp-name">{l.name}<span className="lp-sub">{l.sub}</span></span>
            </div>
          )}
        </div>
      </div>);
  }

  const row = [...logos, ...logos];
  return (
    <div className="logoband" aria-label="Kunden">
      <div className="logoband__track">
        {row.map((l, i) =>
        <div className="logo-ph logo-img" key={(l.id || l.name) + '-' + i} title={l.name}>
            <img className="lp-logo" src={l.image} alt={l.name} loading="lazy" />
          </div>
        )}
      </div>
    </div>);

}

/* ---- Cookie-Consent (schlicht, nicht aufdringlich) ----
   Setzt nur bei aktiver Zustimmung das Cookie `tracking_consent=yes`, das der
   Server (trackVisit) auswertet. Ohne Zustimmung wird nichts getrackt (DSG-konform).
   Die Entscheidung wird in localStorage gemerkt, damit der Banner nicht wiederkehrt. */
const CONSENT_KEY = 'bv_cookie_consent';
const CONSENT_MAX_AGE = 60 * 60 * 24 * 180; // 180 Tage
function setConsentCookie(on) {
  document.cookie = on
    ? `tracking_consent=yes; path=/; max-age=${CONSENT_MAX_AGE}; SameSite=Lax`
    : 'tracking_consent=; path=/; max-age=0; SameSite=Lax';
}
function CookieConsent({ go }) {
  const [show, setShow] = useStateUI(false);
  useEffectUI(() => {
    const choice = localStorage.getItem(CONSENT_KEY);
    if (choice === 'yes') { setConsentCookie(true); return; } // Cookie erneuern
    if (choice === 'no') return;
    const t = setTimeout(() => setShow(true), 800); // dezent verzögert einblenden
    return () => clearTimeout(t);
  }, []);
  const decide = (accept) => {
    localStorage.setItem(CONSENT_KEY, accept ? 'yes' : 'no');
    setConsentCookie(accept);
    setShow(false);
  };
  if (!show) return null;
  return (
    <div className="cookie-consent" role="dialog" aria-label="Cookie-Hinweis">
      <p className="cc-text">
        Wir erfassen anonymisierte Besucherstatistiken — aber nur mit Deiner Zustimmung.{' '}
        <a className="cc-link" onClick={() => go && go('datenschutz')}>Mehr dazu</a>
      </p>
      <div className="cc-actions">
        <button className="cc-btn cc-decline" onClick={() => decide(false)}>Nur notwendige</button>
        <button className="cc-btn cc-accept" onClick={() => decide(true)}>Akzeptieren</button>
      </div>
    </div>);

}

Object.assign(window, {
  Arrow, Logo, Btn, LinkArrow, Reveal, Marquee, MediaPlaceholder, Social, Nav, Footer, NAV_ITEMS,
  SocialGlyph, FloatingSocials, LogoBand, submitContact, CookieConsent
});
