/* BifferProxy — shared site components.
   Must load AFTER React + Babel, BEFORE page scripts. */

const { useState, useEffect, useRef, useCallback, useMemo } = React;

/* ---------- Scroll reveal hook ---------- */
function useReveal() {
  useEffect(() => {
    // Mark above-fold items synchronously BEFORE adding the js-reveal class,
    // so they never get a chance to flash hidden.
    const all = document.querySelectorAll('.reveal');
    const vh = window.innerHeight;
    all.forEach(el => {
      const r = el.getBoundingClientRect();
      if (r.top < vh * 0.95 && r.bottom > -50) {
        el.classList.add('in');
      }
    });
    document.documentElement.classList.add('js-reveal');
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add('in');
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.08, rootMargin: '0px 0px -60px 0px' });
    document.querySelectorAll('.reveal:not(.in)').forEach(el => io.observe(el));
    return () => io.disconnect();
  }, []);
}

/* ---------- Cursor-tracking spotlight ---------- */
function useSpotlight(ref) {
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      el.style.setProperty('--mx', (e.clientX - r.left) + 'px');
      el.style.setProperty('--my', (e.clientY - r.top) + 'px');
    };
    el.addEventListener('mousemove', onMove);
    return () => el.removeEventListener('mousemove', onMove);
  }, []);
}

/* ---------- Logo mark ---------- */
const BifferMark = ({ size = 22, className = '' }) => (
  <svg viewBox="0 0 64 64" width={size} height={size} className={className} aria-hidden="true">
    <rect x="8" y="12" width="32" height="10" rx="2" fill="currentColor"></rect>
    <rect x="8" y="27" width="44" height="10" rx="2" fill="currentColor" opacity="0.55"></rect>
    <rect x="8" y="42" width="26" height="10" rx="2" fill="currentColor"></rect>
  </svg>
);
window.BifferMark = BifferMark;

/* ---------- Nav ---------- */
const Nav = ({ active = 'home' }) => {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  const items = [
    ['Product',       'index.html',        'home'],
    ['How it works',  'how-it-works.html', 'how'],
    ['Security',      'security.html',     'security'],
    ['Integrations',  'integrations.html', 'integrations'],
    ['Roadmap',       'roadmap.html',      'roadmap'],
    ['Docs',          '#docs',             'docs'],
  ];
  return (
    <nav className={'nav ' + (scrolled ? 'scrolled' : '')}>
      <div className="nav-inner">
        <a href="index.html" className="brand">
          <span className="brand-mark"><BifferMark size={22}/></span>
          <span className="brand-word">Biffer<span className="proxy">Proxy</span></span>
        </a>
        <div className="nav-links">
          {items.map(([label, href, key]) => (
            <a key={key} href={href} className={active === key ? 'active' : ''}>{label}</a>
          ))}
        </div>
        <div className="nav-cta">
          <a href="contact.html" className="btn-pill ghost" style={{display:'inline-flex'}}>Sign in</a>
          <a href="contact.html" className="btn-pill primary">Book a demo <span className="arrow">→</span></a>
        </div>
      </div>
    </nav>
  );
};
window.Nav = Nav;

/* ---------- Footer ---------- */
const Footer = () => {
  const [lang, setLang] = useState('EN');
  return (
    <footer className="site-foot">
      <div className="container">
        <div className="foot-grid">
          <div className="foot-col">
            <a href="index.html" className="brand" style={{marginBottom: 16}}>
              <span className="brand-mark"><BifferMark size={22}/></span>
              <span className="brand-word">Biffer<span className="proxy">Proxy</span></span>
            </a>
            <p>Self-hosted LLM proxy. Redacts sensitive data before it reaches any third-party model. Restores on the way back.</p>
          </div>
          <div className="foot-col">
            <h4>Product</h4>
            <ul>
              <li><a href="how-it-works.html">How it works</a></li>
              <li><a href="integrations.html">Integrations</a></li>
              <li><a href="roadmap.html">Roadmap</a></li>
              <li><a href="#changelog">Changelog</a></li>
            </ul>
          </div>
          <div className="foot-col">
            <h4>Security</h4>
            <ul>
              <li><a href="security.html">Compliance</a></li>
              <li><a href="#threat-model">Threat model</a></li>
              <li><a href="#security-txt">security.txt</a></li>
              <li><a href="#dpa">DPA</a></li>
            </ul>
          </div>
          <div className="foot-col">
            <h4>Company</h4>
            <ul>
              <li><a href="#careers">Careers</a></li>
              <li><a href="contact.html">Contact</a></li>
              <li><a href="#press">Press kit</a></li>
            </ul>
          </div>
          <div className="foot-col">
            <h4>Legal</h4>
            <ul>
              <li><a href="#privacy">Privacy</a></li>
              <li><a href="#terms">Terms</a></li>
              <li><a href="#dpa">DPA</a></li>
              <li><a href="#license">License</a></li>
            </ul>
          </div>
        </div>
        <div className="foot-bottom">
          <div style={{display:'flex', alignItems:'center', gap: 14}}>
            <span className="brand-mark" style={{color:'var(--ink-faint)', display:'inline-flex'}}><BifferMark size={14}/></span>
            <span>© 2026 Biffer Labs SAS · Paris · Amsterdam</span>
          </div>
          <div style={{display:'flex', alignItems:'center', gap: 16}}>
            <div className="lang-switch">
              {['EN','FR'].map(l => (
                <button key={l} className={lang===l?'on':''} onClick={() => setLang(l)}>{l}</button>
              ))}
            </div>
            <span className="commit">build 2026.04.17 · a7f3d2c</span>
          </div>
        </div>
      </div>
    </footer>
  );
};
window.Footer = Footer;

/* ---------- Entity tag ---------- */
const Tag = ({ type, label, children }) => {
  const cls = {
    PERSON:'tag--person', EMAIL_ADDRESS:'tag--email', PHONE_NUMBER:'tag--phone',
    ORGANIZATION:'tag--org', LOCATION:'tag--location', DATE_TIME:'tag--date',
    CREDIT_CARD:'tag--card', IP_ADDRESS:'tag--ip', IBAN:'tag--card',
    URL:'tag--ip', US_SSN:'tag--card', AMOUNT:'tag--date'
  }[type] || '';
  return <span className={`tag ${cls}`}>{children || label}</span>;
};
window.Tag = Tag;

/* ---------- Animated hero demo ----------
   Loops: type prompt → morph to tokens → send → response streams back → de-tokenize
*/
const ORIGINAL = `Transfer €450,000 to Marie Dupont at ACME Corp, marie@acme.eu, by Friday.`;
const REDACTED = `Transfer [AMOUNT_1] to [PERSON_1] at [ORG_1], [EMAIL_1], by [DATE_1].`;

// Map of original spans → redacted spans with types.
// We render both versions by tokenizing against known substrings.
const HERO_ENTITIES = [
  { o: '€450,000',         r: '[AMOUNT_1]', type: 'DATE_TIME' },
  { o: 'Marie Dupont',     r: '[PERSON_1]', type: 'PERSON' },
  { o: 'ACME Corp',        r: '[ORG_1]',    type: 'ORGANIZATION' },
  { o: 'marie@acme.eu',    r: '[EMAIL_1]',  type: 'EMAIL_ADDRESS' },
  { o: 'Friday',           r: '[DATE_1]',   type: 'DATE_TIME' },
];

const RESPONSE_TMPL = `Scheduled. [AMOUNT_1] will be sent to [PERSON_1] at [ORG_1] on [DATE_1]. Confirmation goes to [EMAIL_1].`;
const RESPONSE_REAL = `Scheduled. €450,000 will be sent to Marie Dupont at ACME Corp on Friday. Confirmation goes to marie@acme.eu.`;

function splitByEntities(text, entities, useRedacted) {
  // Returns array of {text, entity?}
  const parts = [];
  let i = 0;
  while (i < text.length) {
    let hit = null;
    for (const e of entities) {
      const needle = useRedacted ? e.r : e.o;
      if (text.startsWith(needle, i)) { hit = { e, needle }; break; }
    }
    if (hit) {
      parts.push({ text: hit.needle, entity: hit.e });
      i += hit.needle.length;
    } else {
      // scan until next entity start
      let j = i + 1;
      while (j < text.length) {
        let h = false;
        for (const e of entities) {
          const needle = useRedacted ? e.r : e.o;
          if (text.startsWith(needle, j)) { h = true; break; }
        }
        if (h) break;
        j++;
      }
      parts.push({ text: text.slice(i, j) });
      i = j;
    }
  }
  return parts;
}

const HeroDemo = () => {
  // Phases: 0 typing, 1 typed (pause), 2 redacting (morph), 3 redacted+sending, 4 response streaming, 5 de-redacting, 6 settle
  const [phase, setPhase] = useState(0);
  const [typed, setTyped] = useState(0);          // chars typed
  const [redactedMask, setRedactedMask] = useState(0); // how many entities morphed to tokens
  const [respTyped, setRespTyped] = useState(0);
  const [deMask, setDeMask] = useState(0);
  const [cycle, setCycle] = useState(0);
  const timers = useRef([]);

  const schedule = (fn, ms) => timers.current.push(setTimeout(fn, ms));
  const clear = () => { timers.current.forEach(clearTimeout); timers.current = []; };

  useEffect(() => {
    clear();
    setPhase(0); setTyped(0); setRedactedMask(0); setRespTyped(0); setDeMask(0);

    // 1. Type
    const typeSpeed = 22;
    for (let i = 1; i <= ORIGINAL.length; i++) {
      schedule(() => setTyped(i), i * typeSpeed);
    }
    const typeDone = ORIGINAL.length * typeSpeed;
    schedule(() => setPhase(1), typeDone);

    // 2. Pause, then redact entities one by one
    const pauseAfterType = 400;
    schedule(() => setPhase(2), typeDone + pauseAfterType);
    for (let k = 1; k <= HERO_ENTITIES.length; k++) {
      schedule(() => setRedactedMask(k), typeDone + pauseAfterType + k * 280);
    }
    const redactDone = typeDone + pauseAfterType + HERO_ENTITIES.length * 280;

    // 3. Send to LLM — pause
    schedule(() => setPhase(3), redactDone + 100);

    // 4. Response streams back (redacted)
    const respStart = redactDone + 900;
    schedule(() => setPhase(4), respStart);
    for (let i = 1; i <= RESPONSE_TMPL.length; i++) {
      schedule(() => setRespTyped(i), respStart + i * 14);
    }
    const respDone = respStart + RESPONSE_TMPL.length * 14;

    // 5. De-redact entities
    schedule(() => setPhase(5), respDone + 300);
    for (let k = 1; k <= HERO_ENTITIES.length; k++) {
      schedule(() => setDeMask(k), respDone + 300 + k * 240);
    }
    const deDone = respDone + 300 + HERO_ENTITIES.length * 240;

    // 6. Settle + loop
    schedule(() => setPhase(6), deDone + 200);
    schedule(() => setCycle(c => c + 1), deDone + 2400);

    return clear;
  }, [cycle]);

  const showRedacted = phase >= 2;  // start swapping tokens in
  const showResponse = phase >= 4;
  const showDeRedacted = phase >= 5;

  // Build prompt rendering
  const typingText = ORIGINAL.slice(0, typed);
  const promptParts = useMemo(() => {
    if (phase < 2) {
      return [{ text: typingText }];
    }
    // Replace the first N entities with tokens based on redactedMask
    const parts = splitByEntities(ORIGINAL, HERO_ENTITIES, false);
    let replaced = 0;
    return parts.map(p => {
      if (p.entity) {
        const shouldReplace = replaced < redactedMask;
        replaced++;
        if (shouldReplace) {
          return { text: p.entity.r, entity: p.entity, replaced: true };
        }
        return { text: p.entity.o, entity: p.entity, replaced: false };
      }
      return p;
    });
  }, [phase, typingText, redactedMask]);

  const responseParts = useMemo(() => {
    if (!showResponse) return [];
    const text = RESPONSE_TMPL.slice(0, respTyped);
    const parts = splitByEntities(text, HERO_ENTITIES, true);
    let revealed = 0;
    return parts.map(p => {
      if (p.entity) {
        const show = revealed < deMask;
        revealed++;
        if (show) return { text: p.entity.o, entity: p.entity, revealed: true };
        return { text: p.entity.r, entity: p.entity, revealed: false };
      }
      return p;
    });
  }, [showResponse, respTyped, deMask]);

  const sentActive = phase === 3 || phase === 4;
  const respActive = phase === 4;
  const returnActive = phase >= 5;

  return (
    <div className="hero-demo glass">
      <div className="hd-head">
        <div className="hd-dots">
          <span></span><span></span><span></span>
        </div>
        <div className="hd-title">
          <span className="mono">biffer-proxy</span>
          <span className="hd-sep">—</span>
          <span className="mono dim">session 4a2f · ttl 60m</span>
        </div>
        <div className="hd-status">
          <span className={'hd-led ' + (phase >= 2 ? 'on' : '')}></span>
          <span className="mono">{phase < 2 ? 'listening' : phase < 5 ? 'redacting' : 'restoring'}</span>
        </div>
      </div>

      <div className="hd-body">
        <div className="hd-label">
          <span className="hd-caret">{'›'}</span>
          <span className="mono">PROMPT</span>
          <span className="hd-hint mono">{phase < 2 ? 'typed by user' : phase < 3 ? 'entities detected' : 'ready'}</span>
        </div>
        <div className="hd-prompt">
          {promptParts.map((p, i) => (
            p.entity ? (
              <span key={i} className={'hd-span ' + (p.replaced ? 'swapped' : 'pending')}>
                {p.replaced ? <Tag type={p.entity.type}>{p.text}</Tag> : p.text}
              </span>
            ) : <span key={i}>{p.text}</span>
          ))}
          {phase === 0 && <span className="hd-cursor">▋</span>}
        </div>

        <div className={'hd-pipe ' + (sentActive ? 'active' : '')}>
          <div className="hd-pipe-line"><span className="hd-pkt"></span></div>
          <div className="hd-pipe-label">
            <span className="mono">→ gpt-5 (openai)</span>
            <span className="mono dim">tls · 182 ms</span>
          </div>
        </div>

        <div className={'hd-label response ' + (showResponse ? 'on' : '')}>
          <span className="hd-caret">{'‹'}</span>
          <span className="mono">RESPONSE</span>
          <span className="hd-hint mono">{returnActive ? 'restored' : 'streaming'}</span>
        </div>
        <div className="hd-prompt response">
          {responseParts.map((p, i) => (
            p.entity ? (
              <span key={i} className={'hd-span ' + (p.revealed ? 'restored' : 'redacted')}>
                {p.revealed ? p.text : <Tag type={p.entity.type}>{p.text}</Tag>}
              </span>
            ) : <span key={i}>{p.text}</span>
          ))}
          {respActive && respTyped < RESPONSE_TMPL.length && <span className="hd-cursor dim">▋</span>}
        </div>

        <div className={'hd-pipe return ' + (returnActive ? 'active' : '')}>
          <div className="hd-pipe-line reverse"><span className="hd-pkt"></span></div>
          <div className="hd-pipe-label">
            <span className="mono">← user · de-pseudonymized</span>
            <span className="mono dim">0 PII leaked</span>
          </div>
        </div>
      </div>

      <div className="hd-foot">
        <div className="hd-stat"><span className="mono dim">detected</span><span className="mono">{Math.min(redactedMask, 5)}/5</span></div>
        <div className="hd-stat"><span className="mono dim">proxy</span><span className="mono">118 ms</span></div>
        <div className="hd-stat"><span className="mono dim">audit</span><span className="mono">logged ✓</span></div>
      </div>
    </div>
  );
};
window.HeroDemo = HeroDemo;

/* ---------- Counter ---------- */
const Counter = ({ to, suffix = '', duration = 1400, prefix = '', format = (n) => n.toLocaleString() }) => {
  const ref = useRef(null);
  const [v, setV] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let started = false;
    const io = new IntersectionObserver((es) => {
      if (es[0].isIntersecting && !started) {
        started = true;
        const start = performance.now();
        const tick = (t) => {
          const p = Math.min(1, (t - start) / duration);
          const eased = 1 - Math.pow(1 - p, 3);
          setV(to * eased);
          if (p < 1) requestAnimationFrame(tick);
          else setV(to);
        };
        requestAnimationFrame(tick);
      }
    }, { threshold: 0.4 });
    io.observe(el);
    return () => io.disconnect();
  }, [to, duration]);
  return <span ref={ref} className="tnum">{prefix}{format(to === Math.floor(to) ? Math.floor(v) : v.toFixed(1))}{suffix}</span>;
};
window.Counter = Counter;

/* ---------- Motion annotation pin ---------- */
const MotionPin = ({ children, style }) => (
  <span className="motion-pin" style={style}>{children}</span>
);
window.MotionPin = MotionPin;

window.useReveal = useReveal;
window.useSpotlight = useSpotlight;

Object.assign(window, { Nav, Footer, BifferMark, Tag, HeroDemo, Counter, MotionPin, useReveal, useSpotlight });
