// Core components — v2 magazine kit
// Chrome (folio nav, colophon), the rank numeral, verdict block, feature
// entries, index rows, plates with mandatory captions, reveals, save control.
const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;

// ————— Reveal: scroll-triggered, once, reduced-motion safe —————
function useInView(threshold) {
  const ref = useRefC(null);
  const [inView, setInView] = useStateC(false);
  useEffectC(() => {
    const el = ref.current;
    if (!el || !('IntersectionObserver' in window)) { setInView(true); return; }
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); io.disconnect(); }
    }, { threshold: threshold ?? 0.2 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

function Reveal({ children, style, as }) {
  const [ref, on] = useInView(0.15);
  const Tag = as || 'div';
  return <Tag ref={ref} className={'reveal' + (on ? ' in' : '')} style={style}>{children}</Tag>;
}

// ————— Plate: real imagery with tint fallback, caption mandatory —————
// `src` points at /images per the manifest; until the file exists the tint
// plate shows (the img hides itself on error).
function Plate({ tint, src, alt, caption, credit, ratio, height, zoom, style, dark }) {
  return (
    <figure style={{ margin: 0, ...style }}>
      <div className={'plate-img plate-grain' + (ratio ? ' ' + ratio : '')} style={{ height: height || (ratio ? 'auto' : '100%'), width: '100%' }}>
        <div className={zoom ? 'slow-zoom' : ''} style={{ position: 'absolute', inset: 0, background: tint }}>
          {src && (
            <img src={src} alt={alt || caption || ''} loading="lazy"
              style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }}
              onError={e => { e.currentTarget.style.display = 'none'; }} />
          )}
        </div>
      </div>
      {caption && (
        <figcaption className="photo-caption caption" style={dark ? { color: 'var(--folio-on-plate)' } : null}>
          <span>{caption}</span>
          <span className="credit" style={{ opacity: 0.7 }}>{credit || 'Luxehotels'}</span>
        </figcaption>
      )}
    </figure>
  );
}

// ————— Rank numeral — the brand atom —————
function RankNumeral({ rank, size }) {
  return (
    <div className="rank-numeral num" style={{ fontSize: size || 'clamp(96px, 12vw, 180px)' }}>
      <span style={{ fontSize: '0.32em', verticalAlign: '0.9em', letterSpacing: 0, marginRight: '0.12em' }}>№</span>{rank}
    </div>
  );
}

// ————— Eyebrow / badges / tags —————
function Eyebrow({ children, accent, style }) {
  return <div className="eyebrow" style={{ color: accent ? 'var(--verdict)' : undefined, ...style }}>{children}</div>;
}

function AccoladeBadge({ label }) {
  return (
    <span className="utility" style={{ fontSize: 11.5, letterSpacing: '0.08em', textTransform: 'uppercase', padding: '7px 12px', border: '1px solid var(--hairline)', whiteSpace: 'nowrap' }}>
      {label}
    </span>
  );
}

function ArchetypeTag({ children }) {
  return (
    <span className="utility" style={{ fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', padding: '6px 11px', border: '1px solid var(--hairline)', color: 'var(--folio)', whiteSpace: 'nowrap' }}>
      {children}
    </span>
  );
}

// ————— Suitcase (saved) —————
function getSaved() { try { return JSON.parse(localStorage.getItem('lh_saved') || '[]'); } catch { return []; } }
function setSaved(ids) {
  localStorage.setItem('lh_saved', JSON.stringify(ids));
  window.dispatchEvent(new Event('lh_saved_change'));
}
function SaveControl({ id, dark }) {
  const [saved, setSavedState] = useStateC(() => getSaved().includes(id));
  useEffectC(() => {
    const h = () => setSavedState(getSaved().includes(id));
    window.addEventListener('lh_saved_change', h);
    return () => window.removeEventListener('lh_saved_change', h);
  }, [id]);
  const toggle = (e) => {
    e.preventDefault(); e.stopPropagation();
    const ids = getSaved();
    setSaved(saved ? ids.filter(x => x !== id) : [...ids, id]);
  };
  return (
    <button onClick={toggle} aria-pressed={saved} aria-label={saved ? 'Remove from suitcase' : 'Add to suitcase'}
      title={saved ? 'In your suitcase' : 'Add to suitcase'}
      className="utility"
      style={{ fontSize: 15, lineHeight: 1, color: saved ? 'var(--verdict)' : (dark ? 'var(--folio-on-plate)' : 'var(--folio)'), padding: 6 }}>
      {saved ? '■' : '□'}
    </button>
  );
}

// ————— Folio bar (running header) —————
function FolioBar({ route, overPlate }) {
  const [scrolled, setScrolled] = useStateC(false);
  useEffectC(() => {
    const h = () => setScrolled(window.scrollY > 40);
    h(); window.addEventListener('scroll', h, { passive: true });
    return () => window.removeEventListener('scroll', h);
  }, []);
  const solid = scrolled || !overPlate;
  const [savedCount, setCount] = useStateC(() => getSaved().length);
  useEffectC(() => {
    const h = () => setCount(getSaved().length);
    window.addEventListener('lh_saved_change', h);
    return () => window.removeEventListener('lh_saved_change', h);
  }, []);
  const links = [
    ['#/100', 'The 100', route.startsWith('/100')],
    ['#/destinations', 'Destinations', route.startsWith('/destinations')],
    ['#/methodology', 'Methodology', route.startsWith('/methodology')],
    ['#/suitcase', savedCount ? `Suitcase (${savedCount})` : 'Suitcase', route.startsWith('/suitcase')],
  ];
  return (
    <header className={'folio-bar ' + (solid ? 'folio-solid' : 'folio-idle')}>
      <div className="container" style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 32, paddingTop: 18, paddingBottom: 18 }}>
        <a href="#/" aria-label="Luxehotels — cover" style={{ fontFamily: 'var(--display)', fontSize: 26, letterSpacing: '0.02em', fontVariationSettings: "'opsz' 144" }}>
          Luxehotels
        </a>
        <nav aria-label="Primary" style={{ display: 'flex', gap: 28, alignItems: 'baseline', flexWrap: 'wrap' }}>
          {links.map(([href, label, active]) => (
            <a key={href} href={href} className="utility"
              style={{ fontSize: 12, letterSpacing: '0.14em', textTransform: 'uppercase',
                borderBottom: active ? '1px solid currentColor' : '1px solid transparent', paddingBottom: 3,
                opacity: active ? 1 : 0.75 }}>
              {label}
            </a>
          ))}
        </nav>
        <span className="utility num folio-edition" style={{ fontSize: 11, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.6, whiteSpace: 'nowrap' }}
          aria-hidden="true">
          The 100 · {window.EDITION} Edition
        </span>
      </div>
    </header>
  );
}

// ————— Colophon footer —————
function Colophon() {
  return (
    <footer className="folio-foot" style={{ marginTop: 'var(--section)', padding: '96px 0 64px', background: 'var(--paper)' }}>
      <div className="container">
        <div style={{ fontFamily: 'var(--display)', fontSize: 44, marginBottom: 40, fontVariationSettings: "'opsz' 144" }}>Luxehotels</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 48, alignItems: 'start' }}>
          <div className="caption" style={{ maxWidth: '38ch' }}>
            An independent annual review of the world's best hotels.
            Every property is scored on twelve dimensions by editors who
            pay their own way. No placement is sold, ever.
          </div>
          <div style={{ display: 'grid', gap: 10 }}>
            {[['#/100', 'The Luxehotels 100'], ['#/destinations', 'The Atlas'], ['#/methodology', 'How we score'], ['#/suitcase', 'Your suitcase']].map(([h, l]) => (
              <a key={h} href={h} className="utility link-quiet" style={{ fontSize: 13, justifySelf: 'start' }}>{l}</a>
            ))}
          </div>
          <div className="caption" style={{ textAlign: 'right' }}>
            <div className="num">The 100 — {window.EDITION} Edition</div>
            <div style={{ marginTop: 8 }}>Set in Fraunces, Source Serif &amp; Inter</div>
            <div style={{ marginTop: 8 }}>© {window.EDITION} Luxehotels.io</div>
          </div>
        </div>
      </div>
    </footer>
  );
}

// ————— Sub-score hairline bars —————
function SubScoreBars({ sub, animate, columns }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: columns || '1fr 1fr', gap: '4px 48px' }}>
      {window.SUBSCORES.map((s, i) => (
        <div key={s} style={{ padding: '14px 0 16px', borderBottom: '1px solid var(--hairline)' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
            <span className="utility" style={{ fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--folio)' }}>{s}</span>
            <span className="num" style={{ fontFamily: 'var(--display)', fontSize: 21 }}>
              {sub[i].toFixed(1)}<span style={{ color: 'var(--folio)', fontSize: 13 }}> /10</span>
            </span>
          </div>
          <div className="bar-track">
            <div className="bar-fill" style={{ width: animate ? `${sub[i] * 10}%` : 0 }} />
          </div>
        </div>
      ))}
    </div>
  );
}

// ————— Feature entry (ranks 1–10 on /100, destination chapters) —————
function FeatureEntry({ p, flip, showRank }) {
  return (
    <Reveal>
      <a href={`#/hotels/${p.id}`} id={showRank !== false && window.isRanked(p) ? `no-${p.rank}` : undefined}
        className={'feature-entry fe-grid' + (flip ? ' flip' : '')} data-rank={p.rank}
        style={{ padding: '72px 0', borderBottom: '1px solid var(--hairline)' }}>
        <div className="fe-img">
          <Plate tint={p.tint} src={p.image} alt={p.alt} ratio="ar-32" caption={`${p.name} · ${p.location}`} credit={p.credit} />
        </div>
        <div className="fe-txt">
          {showRank !== false && window.isRanked(p) && <RankNumeral rank={p.rank} size="clamp(72px, 9vw, 150px)" />}
          <h3 className="display-m" style={{ margin: '20px 0 12px' }}>{p.name}</h3>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 18, marginBottom: 22, flexWrap: 'wrap' }}>
            <Eyebrow>{p.destination} · {p.region}</Eyebrow>
            <span className="num" style={{ fontFamily: 'var(--display)', fontSize: 19, color: 'var(--verdict)' }}>{p.score}<span style={{ color: 'var(--folio)', fontSize: 13 }}>/100</span></span>
            <SaveControl id={p.id} />
          </div>
          <p className="standfirst" style={{ margin: 0, maxWidth: '46ch', color: 'var(--ink)' }}>{p.label}</p>
          <span className="utility link" style={{ display: 'inline-block', marginTop: 26, fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Read the review</span>
        </div>
      </a>
    </Reveal>
  );
}

// ————— Index row (ranks 11–100) —————
function IndexRow({ p }) {
  return (
    <a href={`#/hotels/${p.id}`} id={`no-${p.rank}`} className="index-row" data-rank={p.rank}>
      <span className="rank-numeral num" style={{ fontSize: 26 }}>{p.rank}</span>
      <span style={{ fontFamily: 'var(--display)', fontSize: 22, fontVariationSettings: "'opsz' 60" }}>{p.name}</span>
      <span className="caption index-dest" style={{ letterSpacing: '0.04em' }}>{p.destination} · {p.region}</span>
      <span className="num" style={{ fontFamily: 'var(--display)', fontSize: 19, color: 'var(--verdict)', textAlign: 'right' }}>{p.score}</span>
      <span className="caption index-desc" style={{ fontFamily: 'var(--body)', fontSize: 14.5, color: 'var(--folio)', fontStyle: 'italic' }}>{p.oneLiner}</span>
      <span className="index-save" style={{ textAlign: 'right' }}><SaveControl id={p.id} /></span>
    </a>
  );
}

// ————— Pull quote —————
function PullQuote({ children }) {
  return <blockquote className="pullquote">{children}</blockquote>;
}

// ————— Chapter opener (shared by /100, methodology, destinations) —————
function ChapterOpener({ eyebrow, title, children }) {
  return (
    <section style={{ paddingTop: 200, paddingBottom: 88, borderBottom: '1px solid var(--ink)' }}>
      <div className="container">
        <Eyebrow accent>{eyebrow}</Eyebrow>
        <h1 className="display-xl" style={{ margin: '28px 0 0', maxWidth: '16ch' }}>{title}</h1>
        {children && <div style={{ marginTop: 44, maxWidth: '62ch' }}>{children}</div>}
      </div>
    </section>
  );
}

Object.assign(window, {
  useInView, Reveal, Plate, RankNumeral, Eyebrow, AccoladeBadge, ArchetypeTag,
  SaveControl, getSaved, FolioBar, Colophon, SubScoreBars, FeatureEntry,
  IndexRow, PullQuote, ChapterOpener,
});
