// The Luxehotels 100 (/100) — the flagship ranked list.
// The Pantheon (1–10) as feature entries; Icons / Collection / Connoisseur's
// Cut (11–100) as a typographic index with tier openers.
const { useState: useState100, useEffect: useEffect100, useMemo: useMemo100 } = React;

// Rough USD so the rate-band filter can compare across listed formats
// ("From ~$750", "From ~EUR 1,500", "Four figures", "All-inclusive ~$1,800-3,500 pp").
function usdOf(from) {
  if (!from) return null;
  const s = String(from);
  if (/four figures/i.test(s)) return 1800;
  if (/highest rates/i.test(s)) return 3000;
  const m = s.match(/(EUR|CHF|\$|£)\s?([\d,]+)/);
  if (!m) return null;
  const fx = { EUR: 1.1, CHF: 1.15, '$': 1, '£': 1.3 }[m[1]] || 1;
  return parseInt(m[2].replace(/,/g, ''), 10) * fx;
}
const BANDS = [
  { key: 'under1k', label: 'Under $1,000', test: v => v !== null && v < 1000 },
  { key: '1to2.5k', label: '$1,000–2,500', test: v => v !== null && v >= 1000 && v <= 2500 },
  { key: 'over2.5k', label: 'Over $2,500', test: v => v !== null && v > 2500 },
];

const HOW_TO_USE = [
  ['Match the property to the trip, not the rank.', 'The top of this list is a menu, not a queue. Design and food in a city: Cheval Blanc Paris, Rosewood Hong Kong, Bulgari Tokyo. Romance and seclusion: Passalacqua, Aman Venice, Le Sirenuse. A barefoot reset: Soneva Fushi or Nihi Sumba. Safari: Singita Sabi Sand or andBeyond Bateleur Camp. A first icon: Raffles Singapore, Claridge\'s, Hotel du Cap.'],
  ['Book to a timeline.', 'For the scarcest properties — Passalacqua at 24 rooms, Giraffe Manor at 12, Casa Maria Luigia at 12 — reserve nine to twelve months ahead. Safari camps need twelve months for migration season, July through October in the Mara. Most Amalfi and Riviera icons close roughly November through March.'],
  ['Use a Virtuoso or Amex Fine Hotels & Resorts advisor.', 'The same room booked through a preferred-partner agent typically adds an upgrade, daily breakfast, a resort or spa credit, and flexible check-in at no extra cost. On a four-figure nightly rate, that is free money.'],
  ['Verify before you commit.', 'Copacabana Palace\'s annex is closed until October 2026. Atlantis The Royal paused several marquee restaurants in 2026. If you are booking for a specific room category or restaurant, confirm it is operating.'],
  ['Where luxury is heading.', 'Three trends run through this list. Food is now the deciding dimension at the top, with three-star kitchens inside hotels from Paris to Bangkok. Scarcity beats scale — the fastest risers are properties with fewer than fifty keys. And the branded-residence model, from LVMH\'s Maisons to Aman\'s members clubs, is quietly reshaping what a hotel even is.'],
];

function The100Page({ anchor }) {
  const ranked = useMemo100(() => window.getRanked100(), []);
  const regions = useMemo100(() => [...new Set(ranked.map(p => p.region))].sort(), [ranked]);

  const [drawerOpen, setDrawerOpen] = useState100(false);
  const [region, setRegion] = useState100(null);
  const [archetype, setArchetype] = useState100(null);
  const [band, setBand] = useState100(null);
  const filtering = region || archetype || band;

  const visible = useMemo100(() => ranked.filter(p =>
    (!region || p.region === region) &&
    (!archetype || p.archetype === archetype) &&
    (!band || BANDS.find(b => b.key === band).test(usdOf(p.from)))
  ), [ranked, region, archetype, band]);

  const features = visible.filter(p => p.rank <= 10);
  const index = visible.filter(p => p.rank > 10);
  const pantheon = window.TIERS[0];
  const indexTiers = window.TIERS.slice(1)
    .map(t => ({ ...t, rows: index.filter(p => p.rank >= t.from && p.rank <= t.to) }))
    .filter(t => t.rows.length > 0);

  // Sticky mini-folio: current position in the list.
  const [currentRank, setCurrentRank] = useState100(null);
  useEffect100(() => {
    if (!('IntersectionObserver' in window)) return;
    const io = new IntersectionObserver(entries => {
      entries.forEach(e => { if (e.isIntersecting) setCurrentRank(parseInt(e.target.dataset.rank, 10)); });
    }, { rootMargin: '-40% 0px -55% 0px', threshold: 0 });
    document.querySelectorAll('[data-rank]').forEach(el => io.observe(el));
    return () => io.disconnect();
  }, [visible.length]);

  // Deep link: #/100/{rank} scrolls to that entry.
  useEffect100(() => {
    if (!anchor) return;
    const el = document.getElementById(`no-${anchor}`);
    if (el) setTimeout(() => el.scrollIntoView({ block: 'center' }), 60);
  }, [anchor]);

  const chip = (active, onClick, label) => (
    <button key={label} className={'filter-chip' + (active ? ' on' : '')} onClick={onClick} aria-pressed={active}>{label}</button>
  );

  return (
    <div data-screen-label="02 The 100">
      <ChapterOpener eyebrow={`The definitive list · ${window.EDITION} Edition`} title="The 100.">
        <p className="prose" style={{ margin: 0, fontSize: 19 }}>
          One hundred properties, twelve dimensions, zero sponsored placements.
          We synthesized seven authoritative sources — The World's 50 Best,
          La Liste, Travel + Leisure, Condé Nast Traveler, Forbes Travel Guide
          and the Michelin Keys — then corrected their city bias with editorial
          judgment. No property paid for inclusion or position; where a hotel
          falls short of perfection, we say so.{' '}
          <a href="#/methodology" className="link">How we score →</a>
        </p>
      </ChapterOpener>

      {/* Filter the list — collapsed by default */}
      <div className="container" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: 20, paddingBottom: 20, borderBottom: '1px solid var(--hairline)' }}>
        <span className="caption num">
          {filtering ? `${visible.length} of 100 shown` : `100 properties · ${regions.length} regions`}
        </span>
        <button className="utility" onClick={() => setDrawerOpen(o => !o)} aria-expanded={drawerOpen}
          style={{ fontSize: 12, letterSpacing: '0.12em', textTransform: 'uppercase', display: 'flex', gap: 10, alignItems: 'center' }}>
          {drawerOpen ? 'Close' : 'Filter the list'}
          <span style={{ transition: 'transform 250ms', transform: drawerOpen ? 'rotate(45deg)' : 'none', fontSize: 15 }}>+</span>
        </button>
      </div>
      {drawerOpen && (
        <div className="container" style={{ paddingTop: 32, paddingBottom: 32, borderBottom: '1px solid var(--hairline)', display: 'grid', gap: 24 }}>
          {[
            ['Region', regions.map(r => chip(region === r, () => setRegion(region === r ? null : r), r))],
            ['Archetype', window.ARCHETYPES.map(a => chip(archetype === a, () => setArchetype(archetype === a ? null : a), a))],
            ['Rate', BANDS.map(b => chip(band === b.key, () => setBand(band === b.key ? null : b.key), b.label))],
          ].map(([label, chips]) => (
            <div key={label} style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: 20, alignItems: 'baseline' }}>
              <span className="eyebrow">{label}</span>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>{chips}</div>
            </div>
          ))}
          {filtering && (
            <button className="utility link" style={{ fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', justifySelf: 'start' }}
              onClick={() => { setRegion(null); setArchetype(null); setBand(null); }}>
              Clear all filters
            </button>
          )}
        </div>
      )}

      {/* Ranks 1–10 — The Pantheon */}
      {features.length > 0 && (
        <section className="container">
          {!filtering && (
            <div style={{ padding: '80px 0 8px', maxWidth: '62ch' }}>
              <Eyebrow accent>№ {pantheon.from}–{pantheon.to}</Eyebrow>
              <h2 className="display-l" style={{ margin: '18px 0 14px' }}>{pantheon.title}.</h2>
              <p className="standfirst" style={{ margin: 0, color: 'var(--folio)' }}>{pantheon.desc}</p>
            </div>
          )}
          {features.map((p, i) => <FeatureEntry key={p.id} p={p} flip={i % 2 === 1} />)}
        </section>
      )}

      {/* Ranks 11–100 — the index, in tiers */}
      {indexTiers.map(t => (
        <section key={t.id} className="container" style={{ paddingTop: 96 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10, gap: 24, flexWrap: 'wrap' }}>
            <div>
              <Eyebrow accent>№ {t.from}–{t.to}</Eyebrow>
              <h2 className="display-m" style={{ margin: '14px 0 10px' }}>{t.title}.</h2>
              <p className="standfirst" style={{ margin: '0 0 24px', color: 'var(--folio)', maxWidth: '58ch', fontSize: 17 }}>{t.desc}</p>
            </div>
            <span className="caption num">Ranked by score · {window.EDITION} Edition</span>
          </div>
          <div style={{ borderTop: '1px solid var(--ink)' }}>
            {t.rows.map(p => <IndexRow key={p.id} p={p} />)}
          </div>
        </section>
      ))}

      {visible.length === 0 && (
        <div className="container" style={{ paddingTop: 120, paddingBottom: 120, textAlign: 'center' }}>
          <p className="display-s" style={{ margin: 0 }}>Nothing matches that combination.</p>
          <button className="utility link" style={{ marginTop: 20, fontSize: 13 }} onClick={() => { setRegion(null); setArchetype(null); setBand(null); }}>Clear the filters</button>
        </div>
      )}

      {/* How to use this list */}
      <section className="container-text" style={{ paddingTop: 'var(--section)' }}>
        <Eyebrow accent>The editors' notes</Eyebrow>
        <h2 className="display-m" style={{ margin: '20px 0 40px' }}>How to use this list.</h2>
        {HOW_TO_USE.map(([lead, body]) => (
          <p key={lead} className="prose" style={{ fontSize: 17 }}>
            <strong style={{ fontWeight: 600 }}>{lead}</strong> {body}
          </p>
        ))}
      </section>

      {/* Sticky mini-folio */}
      <div className={'mini-folio num utility' + (currentRank ? ' on' : '')} aria-hidden="true"
        style={{ fontSize: 12, letterSpacing: '0.08em' }}>
        № {currentRank || '—'} <span style={{ color: 'var(--folio)' }}>of 100</span>
      </div>
    </div>
  );
}

Object.assign(window, { The100Page });
