/* global React, ReactDOM */
const { useState, useEffect, useRef, useCallback } = React;

/* Snap scroll — one cinematic view at a time */
function useSectionScroll(pageRef, enabled, letterExitRef) {
  useEffect(() => {
    if (!enabled || !pageRef.current) return;

    const root = document.documentElement;
    const isTouchNav = window.matchMedia('(pointer: coarse)').matches;
    if (isTouchNav) root.classList.add('snap-touch');

    const getSections = () =>
      [...pageRef.current.querySelectorAll(':scope > .sect')];

    let animating = false;
    let animFrame = 0;
    let wheelAccum = 0;
    let wheelResetTimer;
    let snapTimer;

    const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
    const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
    const SCROLL_MS = isTouchNav ? 720 : 1800;
    const LETTER_EXIT_MS = 720;

    const isInteractiveTarget = (target) =>
      target?.closest('button, a, input, textarea, select, .cal-modal, .cal-modal-panel, .scroll-cue');

    const currentIndex = () => {
      const y = window.scrollY + window.innerHeight * 0.22;
      const secs = getSections();
      let idx = 0;
      let best = Infinity;
      for (let i = 0; i < secs.length; i++) {
        const d = Math.abs(secs[i].offsetTop - y);
        if (d < best) { best = d; idx = i; }
      }
      return idx;
    };

    const animateScroll = (targetY, onComplete) => {
      const start = window.scrollY;
      const distance = targetY - start;
      if (Math.abs(distance) < 4) {
        window.scrollTo(0, targetY);
        onComplete?.();
        return;
      }

      if (animFrame) cancelAnimationFrame(animFrame);
      animating = true;
      root.classList.add('scroll-animating');
      const t0 = performance.now();
      const ease = isTouchNav ? easeOutCubic : easeOutQuart;

      const step = (now) => {
        const t = Math.min(1, (now - t0) / SCROLL_MS);
        window.scrollTo(0, start + distance * ease(t));
        if (t < 1) {
          animFrame = requestAnimationFrame(step);
        } else {
          window.scrollTo(0, targetY);
          animFrame = 0;
          animating = false;
          root.classList.remove('scroll-animating');
          onComplete?.();
        }
      };
      animFrame = requestAnimationFrame(step);
    };

    const scrollToSection = (index) => {
      if (animating) return;

      const secs = getSections();
      const i = Math.max(0, Math.min(secs.length - 1, index));
      const from = currentIndex();
      const targetY = secs[i].offsetTop;
      const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      const letterExit = letterExitRef?.current;

      const runScroll = () => {
        animateScroll(targetY, () => {
          if (i === 0) letterExit?.end?.();
        });
      };

      if (from === 0 && i > 0) {
        if (!reducedMotion) letterExit?.start?.();
        const exitMs = reducedMotion ? 0 : LETTER_EXIT_MS;
        if (exitMs > 0) {
          animating = true;
          setTimeout(() => runScroll(), exitMs);
        } else {
          runScroll();
        }
        return;
      }

      if (i === 0 && from > 0) {
        if (!reducedMotion && !letterExit?.isExiting?.()) letterExit?.start?.();
        runScroll();
        return;
      }

      letterExit?.end?.();
      runScroll();
    };

    const onWheel = (e) => {
      if (animating) {
        e.preventDefault();
        return;
      }
      if (Math.abs(e.deltaY) < 6) return;

      wheelAccum += e.deltaY;
      clearTimeout(wheelResetTimer);
      wheelResetTimer = setTimeout(() => { wheelAccum = 0; }, 220);
      if (Math.abs(wheelAccum) < 55) return;

      const idx = currentIndex();
      const dir = wheelAccum > 0 ? 1 : -1;
      wheelAccum = 0;

      const sec = getSections()[idx];
      const rect = sec.getBoundingClientRect();
      if (sec.offsetHeight > window.innerHeight * 1.08) {
        if (dir > 0 && rect.bottom > window.innerHeight + 48) return;
        if (dir < 0 && rect.top < -48) return;
      }

      e.preventDefault();
      scrollToSection(idx + dir);
    };

    let touchStartY = 0;
    let touchStartX = 0;
    let touchStartIndex = 0;
    let touchTracking = false;
    let touchIntent = null;
    let lastTouchNavAt = 0;
    const SWIPE_MIN = isTouchNav ? 44 : 52;
    const SWIPE_COOLDOWN = isTouchNav ? 780 : 0;

    const onTouchStart = (e) => {
      if (isInteractiveTarget(e.target)) return;
      if (animating) {
        e.preventDefault();
        return;
      }
      touchStartY = e.touches[0].clientY;
      touchStartX = e.touches[0].clientX;
      touchStartIndex = currentIndex();
      touchTracking = true;
      touchIntent = null;
    };

    const onTouchMove = (e) => {
      if (!touchTracking || isInteractiveTarget(e.target)) return;
      if (animating) {
        e.preventDefault();
        return;
      }

      const dy = e.touches[0].clientY - touchStartY;
      const dx = e.touches[0].clientX - touchStartX;

      if (!touchIntent && (Math.abs(dy) > 8 || Math.abs(dx) > 8)) {
        touchIntent = Math.abs(dy) >= Math.abs(dx) ? 'vertical' : 'horizontal';
      }

      if (touchIntent === 'vertical') e.preventDefault();
    };

    const onTouchEnd = (e) => {
      if (!touchTracking) return;
      touchTracking = false;
      if (animating || touchIntent !== 'vertical') return;
      if (performance.now() - lastTouchNavAt < SWIPE_COOLDOWN) return;

      const dy = touchStartY - e.changedTouches[0].clientY;
      if (Math.abs(dy) < SWIPE_MIN) return;

      const dir = dy > 0 ? 1 : -1;
      lastTouchNavAt = performance.now();
      scrollToSection(touchStartIndex + dir);
    };

    const onTouchCancel = () => {
      touchTracking = false;
      touchIntent = null;
    };

    const onScrollEnd = () => {
      if (isTouchNav || animating) return;
      clearTimeout(snapTimer);
      snapTimer = setTimeout(() => {
        if (animating || isTouchNav) return;
        const secs = getSections();
        const y = window.scrollY;
        let nearest = secs[0];
        let nearestDist = Infinity;
        for (const s of secs) {
          const d = Math.abs(s.offsetTop - y);
          if (d < nearestDist) { nearestDist = d; nearest = s; }
        }
        if (nearestDist > 24 && nearestDist < window.innerHeight * 0.38) {
          animateScroll(nearest.offsetTop);
        }
      }, 160);
    };

    const onClick = (e) => {
      const cue = e.target.closest('.scroll-cue');
      if (!cue || !pageRef.current.contains(cue)) return;
      e.preventDefault();
      scrollToSection(currentIndex() + 1);
    };

    const onKeyDown = (e) => {
      if (animating) return;
      if (e.target.closest('button, a, input, textarea, select')) return;
      if (e.key === 'ArrowDown' || e.key === 'PageDown' || e.key === ' ') {
        e.preventDefault();
        scrollToSection(currentIndex() + 1);
      } else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
        e.preventDefault();
        scrollToSection(currentIndex() - 1);
      }
    };

    const page = pageRef.current;
    const touchOpts = { passive: false };
    window.addEventListener('wheel', onWheel, { passive: false });
    window.addEventListener('touchstart', onTouchStart, touchOpts);
    window.addEventListener('touchmove', onTouchMove, touchOpts);
    window.addEventListener('touchend', onTouchEnd, { passive: true });
    window.addEventListener('touchcancel', onTouchCancel, { passive: true });
    if (!isTouchNav) window.addEventListener('scroll', onScrollEnd, { passive: true });
    window.addEventListener('keydown', onKeyDown);
    page.addEventListener('click', onClick);

    return () => {
      window.removeEventListener('wheel', onWheel);
      window.removeEventListener('touchstart', onTouchStart);
      window.removeEventListener('touchmove', onTouchMove);
      window.removeEventListener('touchend', onTouchEnd);
      window.removeEventListener('touchcancel', onTouchCancel);
      if (!isTouchNav) window.removeEventListener('scroll', onScrollEnd);
      window.removeEventListener('keydown', onKeyDown);
      page?.removeEventListener('click', onClick);
      clearTimeout(wheelResetTimer);
      clearTimeout(snapTimer);
      if (animFrame) cancelAnimationFrame(animFrame);
      root.classList.remove('scroll-animating');
      root.classList.remove('snap-touch');
    };
  }, [enabled, pageRef]);
}

/* ============================================================
   Envelope — wax-sealed letter, opens on click
   ============================================================ */
const ENVELOPE_ASSETS = [
  'assets/envelope/envelope-closed.png',
  'assets/backgrounds/envelope-bg.png',
];

function preloadImages(urls) {
  return Promise.all(
    urls.map(
      (src) =>
        new Promise((resolve) => {
          const img = new Image();
          let settled = false;
          const done = () => {
            if (settled) return;
            settled = true;
            resolve();
          };
          img.onload = done;
          img.onerror = done;
          img.src = src;
          if (img.complete) done();
        })
    )
  );
}

function Envelope({ onOpen }) {
  return (
    <React.Fragment>
      <div className="env-stage-inner">
        <div className="env-scene">
          <div
            className="env-shell-wrap"
            onClick={onOpen}
            role="button"
            tabIndex={0}
            aria-label="Ava ümbrik"
            onKeyDown={(e) => {
              if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                onOpen();
              }
            }}
          >
            <img
              className="env-asset"
              src="assets/envelope/envelope-closed.png"
              alt="Pulmakutse ümbrik"
              draggable={false}
              fetchPriority="high"
              decoding="sync"
            />
          </div>
        </div>
      </div>
      <div className="env-hint">klõpsa, et avada</div>
    </React.Fragment>
  );
}

/* ============================================================
   Small building blocks
   ============================================================ */
function OrnRule({ glyph = "❦", onPhoto = false, center = true }) {
  return (
    <div className={"rule" + (center ? " center" : "") + (onPhoto ? " on-photo" : "")}>
      <span className="line" />
      <span className="glyph">{glyph}</span>
      <span className="line" />
    </div>
  );
}

const WEDDING_EVENT = {
  title: 'Ilina & Heiti pulm',
  location: 'Puri, Lootsi 8, Tallinn',
  description: 'Ilina ja Heiti pulm — registreerimine ja pulmapeo ootame teid Puri restoranis.',
  start: '20260723T153000',
  end: '20260723T230000',
  startIso: '2026-07-23T15:30:00',
  endIso: '2026-07-23T23:00:00',
};

function getGoogleCalendarUrl() {
  const params = new URLSearchParams({
    action: 'TEMPLATE',
    text: WEDDING_EVENT.title,
    dates: WEDDING_EVENT.start + '/' + WEDDING_EVENT.end,
    details: WEDDING_EVENT.description,
    location: WEDDING_EVENT.location,
  });
  return 'https://calendar.google.com/calendar/render?' + params.toString();
}

function getOutlookCalendarUrl() {
  const params = new URLSearchParams({
    subject: WEDDING_EVENT.title,
    startdt: WEDDING_EVENT.startIso,
    enddt: WEDDING_EVENT.endIso,
    location: WEDDING_EVENT.location,
    body: WEDDING_EVENT.description,
    path: '/calendar/action/compose',
    rru: 'addevent',
  });
  return 'https://outlook.live.com/calendar/0/deeplink/compose?' + params.toString();
}

function downloadCalendarIcs() {
  const ics = [
    'BEGIN:VCALENDAR',
    'VERSION:2.0',
    'PRODID:-//Ilina & Heiti//Pulm//ET',
    'CALSCALE:GREGORIAN',
    'METHOD:PUBLISH',
    'BEGIN:VEVENT',
    'UID:ilina-heiti-pulm-20260723@puri.ee',
    'DTSTAMP:' + new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z'),
    'DTSTART:' + WEDDING_EVENT.start,
    'DTEND:' + WEDDING_EVENT.end,
    'SUMMARY:' + WEDDING_EVENT.title,
    'LOCATION:' + WEDDING_EVENT.location,
    'DESCRIPTION:' + WEDDING_EVENT.description,
    'END:VEVENT',
    'END:VCALENDAR',
  ].join('\r\n');

  const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = 'ilina-heiti-pulm.ics';
  link.click();
  URL.revokeObjectURL(url);
}

function CalendarPicker({ open, onClose }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    document.body.classList.add('locked');
    return () => {
      document.removeEventListener('keydown', onKey);
      document.body.classList.remove('locked');
    };
  }, [open, onClose]);

  if (!open) return null;

  const options = [
    {
      id: 'google',
      label: 'Google Calendar',
      hint: 'Ava veebis või rakenduses',
      action: () => { window.open(getGoogleCalendarUrl(), '_blank', 'noopener,noreferrer'); onClose(); },
    },
    {
      id: 'apple',
      label: 'Apple / iCal',
      hint: 'Laadib alla .ics faili',
      action: () => { downloadCalendarIcs(); onClose(); },
    },
    {
      id: 'outlook',
      label: 'Outlook',
      hint: 'Ava Outlooki kalendris',
      action: () => { window.open(getOutlookCalendarUrl(), '_blank', 'noopener,noreferrer'); onClose(); },
    },
  ];

  const modal = (
    <div className="cal-modal" role="dialog" aria-modal="true" aria-labelledby="cal-modal-title">
      <button type="button" className="cal-modal-backdrop" onClick={onClose} aria-label="Sulge" tabIndex={-1} />
      <div className="cal-modal-panel">
        <div className="cal-modal-header">
          <h2 id="cal-modal-title" className="cal-modal-title">Lisa kalendrisse</h2>
          <button type="button" className="cal-modal-close" onClick={onClose} aria-label="Sulge">×</button>
        </div>
        <p className="cal-modal-sub">Vali endale sobiv kalender</p>
        <ul className="cal-modal-list">
          {options.map(opt => (
            <li key={opt.id}>
              <button type="button" className="cal-modal-option" onClick={opt.action}>
                <span className="cal-modal-option-label">{opt.label}</span>
                <span className="cal-modal-option-hint">{opt.hint}</span>
              </button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );

  return ReactDOM.createPortal(modal, document.body);
}

function CalendarAddButton({ className, style }) {
  const [open, setOpen] = useState(false);
  return (
    <React.Fragment>
      <button type="button" className={className} style={style} onClick={() => setOpen(true)}>
        Lisa kalendrisse
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
          <rect x="3" y="4" width="18" height="18" rx="2" />
          <path d="M16 2v4M8 2v4M3 10h18" strokeLinecap="round" />
        </svg>
      </button>
      <CalendarPicker open={open} onClose={() => setOpen(false)} />
    </React.Fragment>
  );
}

function Monogram({ onPhoto = false, hero = false }) {
  return (
    <span
      className={"mono" + (onPhoto ? " on-photo" : "")}
      style={{ fontSize: hero ? 'clamp(64px, 11vw, 140px)' : 'clamp(44px, 7vw, 96px)' }}
    >
      <span>Ilina</span><span className="amp">&amp;</span><span>Heiti</span>
    </span>
  );
}

/* Shared invitation copy — first scroll view */
function InvitationContent({ hero = false }) {
  return (
    <div className="invitation-content center stack gap-5">
      <Monogram hero={hero} />
      <div className="eyebrow">kutsuvad Teid osa saama</div>
      <p className="lede">
        oma armastuse loo uuest peatükist
      </p>
      <div className="tiny">23.07.2026 · Puri · Tallinn</div>
    </div>
  );
}

/* Reveal-on-scroll wrapper */
function Reveal({ children, delay = 0, className = "", as: Tag = "div" }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const show = () => {
      el.classList.add('in');
      obs.disconnect();
    };
    const obs = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) show();
    }, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' });
    obs.observe(el);
    requestAnimationFrame(() => {
      const rect = el.getBoundingClientRect();
      if (rect.top < window.innerHeight * 0.92 && rect.bottom > window.innerHeight * 0.08) {
        show();
      }
    });
    return () => obs.disconnect();
  }, []);
  const delayCls = delay ? ` delay-${delay}` : "";
  return <Tag ref={ref} className={`reveal${delayCls} ${className}`}>{children}</Tag>;
}

/* Soft floating hearts — envelope intro & letter hero */
const SOFT_PARTICLES = Array.from({ length: 28 }, (_, i) => {
  const delaySec = i < 18
    ? -((i * 0.55) % 7 + (i % 4) * 0.3)
    : ((i - 18) * 0.22) % 1.8;
  return {
    left: ((i * 13.7 + 4) % 91 + 4).toFixed(1) + '%',
    delay: delaySec.toFixed(1) + 's',
    dur: (10 + (i % 6) * 1.6).toFixed(1) + 's',
    size: 7 + (i % 6) * 2 + (i % 3 === 0 ? 2 : 0),
    op: (0.14 + (i % 5) * 0.032).toFixed(2),
    drift: ((i % 7) - 3) * 9,
    kind: i % 3 === 1 ? 'dot' : 'heart',
  };
});

function SoftParticles({ className = '' }) {
  return (
    <div className={'soft-particles' + (className ? ' ' + className : '')} aria-hidden="true">
      {SOFT_PARTICLES.map((p, i) => (
        <span
          key={i}
          className={'soft-particle soft-particle-' + p.kind}
          style={{
            left: p.left,
            '--delay': p.delay,
            '--dur': p.dur,
            '--size': p.size + 'px',
            '--op': p.op,
            '--drift': p.drift + 'px',
          }}
        />
      ))}
    </div>
  );
}

/* Parallax photo background (gentle) */
function ParallaxPhoto({ src, alt = "" }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    let raf = 0;
    const onScroll = () => {
      if (document.documentElement.classList.contains('scroll-animating')) return;
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        if (document.documentElement.classList.contains('scroll-animating')) return;
        const rect = el.parentElement.getBoundingClientRect();
        const vh = window.innerHeight;
        const progress = (rect.top + rect.height / 2 - vh / 2) / vh;
        const y = Math.max(-40, Math.min(40, -progress * 50));
        el.style.transform = `translateY(${y}px) scale(1.08)`;
      });
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return <img ref={ref} src={src} alt={alt} />;
}

/* ============================================================
   SECTIONS — each is a self-contained scene
   ============================================================ */

/* The letter the envelope reveals — first thing you read.
   Cream paper + centred monogram so the rising letter dissolves into it. */
function LetterHero({ exiting }) {
  return (
    <section className={"sect letter-hero" + (exiting ? " letter-exiting" : "")} data-screen-label="01 Kutse">
      <SoftParticles />
      <div className="letter-panel">
        <div className="sect-inner narrow center stack gap-5">
          <InvitationContent hero />
        </div>
        <div className="scroll-cue dark">
          <span>keri allapoole</span>
          <span className="line" />
        </div>
      </div>
    </section>
  );
}

function DateSection() {
  return (
    <section className="sect photo tall date-slide" data-screen-label="02 Date">
      <div className="sect-bg"><ParallaxPhoto src="assets/photos/real-kiss-sea.jpg" /></div>
      <div className="sect-veil" />
      <div className="sect-inner narrow center align-center stack gap-4 date-slide-copy" style={{ position: 'relative' }}>
        <Reveal>
          <p className="date-line date-line-intro">
            Ootame Teid meie pulmatseremooniale ja sellele järgnevale pulmapeole.
            <span className="date-line-intro-times">
              Welcome &amp; tervitusjoogid algavad kell 15.30
              <br />
              Tseremoonia algab kell 16.00.
            </span>
          </p>
        </Reveal>
        <Reveal delay={1}>
          <p className="date-line date-line-body date-line-body-lg">
            Oleksime väga tänulikud, kui jõuate kohale vähemalt 30 minutit enne
            tseremoonia algust, et saaksime ühiselt nautida tervitusjooke ning
            alustada seda erilist päeva heas seltskonnas ja kaunis meeleolus.
          </p>
        </Reveal>
        <Reveal delay={2}>
          <CalendarAddButton className="btn-ghost on-photo" />
        </Reveal>
      </div>
      <div className="scroll-cue">
        <span>keri allapoole</span>
        <span className="line" />
      </div>
    </section>
  );
}

function VenueSection() {
  const mapsUrl = "https://www.google.com/maps/search/?api=1&query=Puri+restoran+Tallinn";
  return (
    <section className="sect photo tall" data-screen-label="04 Venue">
      <div className="sect-bg"><ParallaxPhoto src="assets/photos/puri-restoran.jpg" alt="Puri restoran" /></div>
      <div className="sect-veil" />
      <div className="sect-inner center align-center narrow stack gap-4" style={{ position: 'relative' }}>
        <Reveal><div className="eyebrow on-photo">Toimumiskoht</div></Reveal>
        <Reveal delay={1}>
          <div className="display-lg">Puri</div>
        </Reveal>
        <Reveal delay={2}>
          <p className="body on-photo">
            Pärast tseremooniat ootavad Teid ühised hetked mere ääres, live-muusika,
            meeleolukas pulmapeo õhtu ning hoolikalt loodud maitseelamused.
          </p>
        </Reveal>
        <Reveal delay={3}>
          <div className="info-card on-photo" style={{ maxWidth: 360, width: '100%' }}>
            <div className="tiny on-photo">Aadress</div>
            <div className="info-address on-photo">Lootsi 8, Tallinn</div>
            <a href={mapsUrl} target="_blank" rel="noreferrer" className="btn-ghost on-photo" style={{ marginTop: 10 }}>
              Ava Google Maps
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                <path d="M7 17 17 7M9 7h8v8" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </a>
          </div>
        </Reveal>
      </div>
      <div className="scroll-cue">
        <span>keri allapoole</span>
        <span className="line" />
      </div>
    </section>
  );
}

function CollapsibleSection({ title, defaultOpen = false, children, className = '', bare = false, compact = false }) {
  const [open, setOpen] = useState(defaultOpen);
  const panelId = useRef('acc-' + title.replace(/\s+/g, '-').toLowerCase()).current;

  return (
    <div
      className={
        'info-accordion'
        + (open ? ' is-open' : '')
        + (bare ? ' info-accordion-bare' : '')
        + (compact ? ' info-accordion-compact' : '')
        + (className ? ' ' + className : '')
      }
      {...(bare ? { 'aria-label': title } : {})}
    >
      {!bare && (
        <button
          type="button"
          className="info-accordion-trigger"
          aria-expanded={open}
          aria-controls={panelId}
          onClick={() => setOpen((o) => !o)}
        >
          <span className="info-accordion-title">{title}</span>
          <span className="info-accordion-icon" aria-hidden="true">▾</span>
        </button>
      )}
      <div id={panelId} className={'info-accordion-panel' + (!open ? ' is-collapsed' : '')}>
        <div className="info-accordion-body">{children}</div>
      </div>
    </div>
  );
}

const DRESSCODE_SWATCHES = [
  { c: '#C0C78D', name: 'Roheline hortensia' },
  { c: '#898F69', name: 'Oliiviroheline' },
  { c: '#8B937E', name: 'Eukalüpti' },
  { c: '#C5AF98', name: 'Beež' },
  { c: '#AD8056', name: 'Karamell' },
  { c: '#9A8266', name: 'Khaki pruun' },
  { c: '#7F5D42', name: 'Tume pruun' },
  { c: '#32415B', name: 'Tumesinine' },
  { c: '#7F8B99', name: 'Sinakashall' },
  { c: '#D0CFCB', name: 'Hele hall' },
  { c: '#D1C4B4', name: 'Taupe' },
  { c: '#D9C9CA', name: 'Hele lillahall' },
];

const FLOWER_LIST = [
  'Pojengid',
  'Hortensiad',
  'Roosid',
];

function GiftIbanBlock() {
  const [copied, setCopied] = useState(false);
  const iban = 'EE34 2200 2210 9378 4471';
  const ibanRaw = 'EE342200221093784471';
  const copy = () => {
    navigator.clipboard?.writeText(ibanRaw);
    setCopied(true);
    setTimeout(() => setCopied(false), 1800);
  };
  return (
    <React.Fragment>
      <div className="iban">
        <div className="stack gap-2">
          <div className="tiny">ILINA LEEK</div>
          <span className="iban-num">{iban}</span>
        </div>
        <button type="button" className="iban-copy" onClick={copy}>
          {copied ? '✓ kopeeritud' : 'kopeeri'}
        </button>
      </div>
    </React.Fragment>
  );
}

function InfoSection() {
  return (
    <section className="sect info-slide" data-screen-label="06 Lisainfo">
      <div className="sect-inner info-slide-inner center stack gap-5">
        <Reveal><div className="eyebrow">Lisainfo</div></Reveal>
        <Reveal delay={1}>
          <div className="info-accordion-list">
            <CollapsibleSection title="Lilled" defaultOpen>
              <p className="body">
                Kui soovite meie päeva lilledega veelgi kaunimaks muuta, rõõmustavad meid kõige enam valged õied
              </p>
              <ul className="flowers">
                {FLOWER_LIST.map((f) => <li key={f}>• {f}</li>)}
              </ul>
            </CollapsibleSection>

            <CollapsibleSection title="Kingitus" defaultOpen>
              <p className="body">
                Suurim kingitus on Teie kohalolek ja ühised mälestused, mida sellel erilisel päeval koos loome.
              </p>
              <p className="body">
                Kui soovite meie rõõmu veelgi kasvatada, võite teha väikese panuse meie unistuste
                mesinädalareisi Mauritiusele.
              </p>
              <p className="body">
                Kingituse võib soovi korral teha ümbrikus või ülekandega meie ühisele kontole:
              </p>
              <GiftIbanBlock />
            </CollapsibleSection>

            <CollapsibleSection title="Pere pisematest" defaultOpen compact>
              <p className="body">
                Et ka teie saaksite õhtut täiel rinnal nautida ja meiega tähistada,
                paluksime sel õhtul nooremad pereliikmed hoidjate hoole alla jätta.
              </p>
            </CollapsibleSection>

            <CollapsibleSection title="Info parkimise kohta" defaultOpen compact>
              <p className="body">
                Puri külalistele mõeldud EuroPargi parkla on pulmapäeval tasuta.
              </p>
              <p className="body">
                Parkimise registreerimiseks palume saabudes edastada restoranile oma
                sõiduki registreerimisnumber.
              </p>
            </CollapsibleSection>

            <CollapsibleSection title="Küsimused ja soovid" defaultOpen className="info-accordion-span">
              <p className="body">
                Kui soovite pulmaõhtul võtta sõna, jagada mõnda mälestust või näidata midagi
                ekraanil, palume sellest eelnevalt teada anda meie pulmaisale.
              </p>
              <p className="body">
                Tema poole võite julgelt pöörduda ka kõigi teiste küsimuste korral nii enne
                pulma kui ka pulmapäeval.
              </p>
              <div className="info-contact">
                <div className="info-contact-name">Karl Pütsep</div>
                <a href="tel:+37255500800" className="info-contact-link">+372 55 500 800</a>
                <a href="mailto:info@pulmaisa.eu" className="info-contact-link">info@pulmaisa.eu</a>
                <a
                  href="https://instagram.com/pulmaisa_karl"
                  className="info-contact-link"
                  target="_blank"
                  rel="noreferrer"
                >
                  Instagram: pulmaisa_karl
                </a>
              </div>
            </CollapsibleSection>
          </div>
        </Reveal>
      </div>
      <div className="scroll-cue">
        <span>keri allapoole</span>
        <span className="line" />
      </div>
    </section>
  );
}

function DresscodeSection() {
  const swatchesLeft = DRESSCODE_SWATCHES.slice(0, 6);
  const swatchesRight = DRESSCODE_SWATCHES.slice(6, 12);

  const renderEdgeSwatches = (items, side) =>
    items.map((s) => (
      <div key={s.name} className={'swatch-item swatch-item-edge swatch-item-' + side}>
        <div className="swatch swatch-edge" style={{ background: s.c }} title={s.name} aria-hidden="true" />
        <span className="swatch-label swatch-label-edge">{s.name}</span>
      </div>
    ));

  return (
    <section className="sect dresscode tall" data-screen-label="05 Dresscode">
      <div className="dresscode-shell">
        <div className="dresscode-copy">
          <div className="eyebrow">Dresscode</div>
          <p className="body dresscode-lede">
            Meil oleks väga hea meel, kui meie päev oleks täidetud pehmete, elegantsete
            ja suviste toonidega.
          </p>
          <p className="body dresscode-lede">
            See ei ole loomulikult kohustuslik, kuid oleksime rõõmsad, kui saaksite
            sellest inspiratsiooni.
          </p>
        </div>

        <div className="dresscode-visual">
          <aside className="dresscode-edge dresscode-edge-left" aria-label="Soovitatavad toonid">
            {renderEdgeSwatches(swatchesLeft, 'left')}
          </aside>
          <figure className="dresscode-photo">
            <img src="assets/photos/dresscode-group.jpg" alt="Dresscode näited — pehmed suvised toonid" />
          </figure>
          <aside className="dresscode-edge dresscode-edge-right" aria-label="Soovitatavad toonid">
            {renderEdgeSwatches(swatchesRight, 'right')}
          </aside>
        </div>

        <div className="swatch-grid dresscode-grid dresscode-grid-mobile" aria-label="Soovitatavad toonid">
          {DRESSCODE_SWATCHES.map((s) => (
            <div key={s.name} className="swatch-item">
              <div className="swatch" style={{ background: s.c }} title={s.name} aria-hidden="true" />
              <span className="swatch-label">{s.name}</span>
            </div>
          ))}
        </div>
      </div>
      <div className="scroll-cue">
        <span>keri allapoole</span>
        <span className="line" />
      </div>
    </section>
  );
}

function RSVPSection() {
  return (
    <section className="sect photo tall finale" data-screen-label="10 RSVP">
      <div className="sect-bg"><ParallaxPhoto src="assets/photos/real-hands-ring.jpg" /></div>
      <div className="sect-veil" />
      <div className="sect-inner narrow center stack gap-5" style={{ position: 'relative' }}>
        <Reveal><div className="eyebrow on-photo">Palume vastata</div></Reveal>
        <Reveal delay={1}>
          <div className="display-md">Hiljemalt 9. juunil 2026</div>
        </Reveal>
        <Reveal delay={2}>
          <OrnRule onPhoto />
        </Reveal>
        <Reveal delay={3}>
          <div style={{ display: 'flex', gap: 14, justifyContent: 'center', flexWrap: 'wrap' }}>
            <button className="btn" onClick={() => alert('Aitäh! Saadame teile RSVP-vormi linki e-posti teel.')}>
              Kinnitan kohaloleku
            </button>
            <button className="btn-ghost on-photo" onClick={() => alert('Aitäh, et andsite teada.')}>
              Saadan vabandused
            </button>
          </div>
        </Reveal>
        <Reveal delay={4}>
          <div className="script">Ilina &amp; Heiti</div>
        </Reveal>
      </div>
    </section>
  );
}

/* ============================================================
   APP
   ============================================================ */
function App() {
  const [phase, setPhase] = useState('envelope'); // 'envelope' | 'transition' | 'page'
  const [envReady, setEnvReady] = useState(false);
  const [introKey, setIntroKey] = useState(0);
  const [letterExiting, setLetterExiting] = useState(false);
  const [progress, setProgress] = useState(0);
  const pageRef = useRef(null);
  const openTimer = useRef(null);
  const letterExitingRef = useRef(false);
  const letterExitRef = useRef({});
  const OPEN_TRANSITION_MS = 900;

  letterExitRef.current = {
    start: () => {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          letterExitingRef.current = true;
          setLetterExiting(true);
        });
      });
    },
    end: () => {
      letterExitingRef.current = false;
      setLetterExiting(false);
    },
    isExiting: () => letterExitingRef.current,
  };

  useEffect(() => {
    document.body.classList.toggle('locked', phase !== 'page');
    document.documentElement.classList.toggle('snap-active', phase === 'page');
  }, [phase]);

  useEffect(() => {
    let active = true;
    setEnvReady(false);
    preloadImages(ENVELOPE_ASSETS).then(() => {
      if (active) setEnvReady(true);
    });
    return () => { active = false; };
  }, [introKey]);

  useSectionScroll(pageRef, phase === 'page', letterExitRef);

  const startOpen = useCallback(() => {
    if (phase !== 'envelope') return;
    window.scrollTo(0, 0);
    setPhase('transition');
    clearTimeout(openTimer.current);
    openTimer.current = setTimeout(() => setPhase('page'), OPEN_TRANSITION_MS);
  }, [phase]);

  useEffect(() => () => clearTimeout(openTimer.current), []);

  const restart = () => {
    clearTimeout(openTimer.current);
    letterExitingRef.current = false;
    setLetterExiting(false);
    window.scrollTo({ top: 0, behavior: 'smooth' });
    setTimeout(() => {
      setPhase('envelope');
      setIntroKey((k) => k + 1);
    }, 400);
  };

  // scroll progress
  useEffect(() => {
    if (phase !== 'page') return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const h = document.documentElement.scrollHeight - window.innerHeight;
        setProgress(h > 0 ? Math.min(1, window.scrollY / h) : 0);
      });
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, [phase]);

  return (
    <>
      <div
        key={introKey}
        className={
          "env-stage"
          + (envReady ? " env-ready" : "")
          + (phase === 'transition' ? " closing" : "")
          + (phase === 'page' ? " hide" : "")
        }
      >
        <SoftParticles className="soft-particles-envelope" />
        <Envelope onOpen={startOpen} />
      </div>

      <div
        ref={pageRef}
        className={"page" + (phase === 'page' ? " in" : "")}
        data-screen-label="Ilina & Heiti"
      >
        <LetterHero exiting={letterExiting} />
        <DateSection />
        <VenueSection />
        <DresscodeSection />
        <InfoSection />
        <RSVPSection />
      </div>

      <div className="progress" aria-hidden={phase !== 'page'}>
        <div className="progress-bar" style={{ transform: `scaleX(${progress})` }} />
      </div>

      <button className="restart" onClick={restart}>algusesse</button>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />);
