// ============================================================
// Land of Festival — main App
// ============================================================
/* global React, ReactDOM, LOF_Parsers, IndiaMap, Header, SidePanel, Timeline, MW, MONTHS_FULL, MONTHS_SHORT */

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

function App() {
  const [festivals, setFestivals] = useState(null);
  const [topo, setTopo] = useState(null);
  const [loadError, setLoadError] = useState(null);

  useEffect(() => {
    Promise.all([
      fetch('data/festivals.json').then((r) => r.json()),
      fetch('data/india_states.topo.json').then((r) => r.json()),
    ])
      .then(([f, t]) => { setFestivals(f); setTopo(t); })
      .catch((e) => { console.error(e); setLoadError(e.message); });
  }, []);

  const processed = useMemo(() => {
    if (!festivals) return null;
    return festivals.map((f, i) => {
      const parsed = LOF_Parsers.parseTime(f.Typical_Time_of_Year);
      const { pan, list } = LOF_Parsers.splitStates(f.Primary_State_or_UT_or_Region);
      return {
        id: 'f' + i,
        name: f.Festival_Name,
        level: f.Festival_Level,
        states: list,
        pan,
        zone: f.Zone,
        type: f.Festival_Type,
        timeRaw: f.Typical_Time_of_Year,
        timeLabel: parsed.label,
        lunar: parsed.lunar,
        weeks: parsed.weeks,
        description: f.Cultural_Scientific_Description,
      };
    });
  }, [festivals]);

  const festivalsByWeek = useMemo(() => {
    if (!processed) return {};
    const counts = {};
    processed.forEach((f) => {
      f.weeks.forEach((w) => { counts[w] = (counts[w] || 0) + 1; });
    });
    return counts;
  }, [processed]);

  const [week, setWeek] = useState(() => {
    try {
      const v = +localStorage.getItem('lof_week');
      if (v >= 1 && v <= 52) return v;
    } catch (e) {}
    return 1;
  });
  useEffect(() => {
    try { localStorage.setItem('lof_week', String(week)); } catch (e) {}
  }, [week]);

  const [hoveredPin, setHoveredPin] = useState(null);
  const [selectedPin, setSelectedPin] = useState(null);
  const prevWeek = useRef(week);
  useEffect(() => {
    if (prevWeek.current !== week) {
      setSelectedPin(null);
      prevWeek.current = week;
    }
  }, [week]);

  const weekFestivals = useMemo(() => {
    if (!processed) return [];
    return processed.filter((f) => f.weeks.has(week));
  }, [processed, week]);

  const pins = useMemo(() => {
    const out = [];
    weekFestivals.forEach((f) => {
      if (f.pan) {
        out.push({ id: f.id, festival: f, pan: true });
      } else {
        f.states.forEach((st) => {
          out.push({ id: f.id, state: st, festival: f });
        });
      }
    });
    return out;
  }, [weekFestivals]);

  const fallbackFacts = useMemo(() => [
    'India has more public-facing festivals than calendar days. The "official holidays" you grew up with are a tiny island in a much wider sea.',
    'Almost every village in rural Maharashtra holds an annual jatra — a fair for the local deity that doubles as a market, social gathering, and matchmaking event.',
    'Many festivals tracked agricultural science long before modern almanacs: when to sow, when to harvest, when to let the soil rest, when the rivers would rise.',
    'Tribal festivals across the Northeast — Wangala, Sekrenyi, Hornbill, Ali-Ai-Ligang — preserve agrarian knowledge that has survived for centuries outside the mainstream calendar.',
    'A "school holiday" is a poor proxy for cultural memory. The festivals that stop being celebrated are usually the ones that stop being remembered.',
  ], []);
  const factPool = useMemo(() => {
    if (!processed) return fallbackFacts;
    const local = weekFestivals
      .map((f) => `${f.name} — ${f.description}`)
      .filter(Boolean);
    return local.length > 0 ? local : fallbackFacts;
  }, [weekFestivals, processed, fallbackFacts]);
  const [factIdx, setFactIdx] = useState(0);
  useEffect(() => { setFactIdx(0); }, [week]);
  useEffect(() => {
    if (factPool.length <= 1) return;
    const id = setInterval(() => setFactIdx((i) => (i + 1) % factPool.length), 7000);
    return () => clearInterval(id);
  }, [factPool]);
  const didYouKnow = factPool[factIdx % factPool.length] || '';

  const mapHostRef = useRef(null);
  const [mapBox, setMapBox] = useState({ w: 600, h: 500 });
  useEffect(() => {
    function measure() {
      const el = mapHostRef.current;
      if (!el) return;
      const r = el.getBoundingClientRect();
      const w = Math.max(280, r.width);
      const h = Math.max(280, r.height);
      setMapBox({ w, h });
    }
    measure();
    const t = setTimeout(measure, 80);
    window.addEventListener('resize', measure);
    return () => { window.removeEventListener('resize', measure); clearTimeout(t); };
  }, [processed, topo]);

  if (loadError) {
    return (
      <div style={{ padding: 40, color: 'var(--paper)' }}>
        <h2>Could not load data: {loadError}</h2>
      </div>
    );
  }
  if (!processed || !topo) {
    return <LoadingScreen />;
  }

  return (
    <div style={{
      height: '100vh',
      display: 'flex',
      flexDirection: 'column',
      overflow: 'hidden',
    }}>
      <Header week={week} weekCount={weekFestivals.length} />

      <main style={{
        flex: 1,
        minHeight: 0,
        padding: '12px 56px 12px',
        display: 'grid',
        gridTemplateColumns: '1fr 420px',
        gap: 32,
        alignItems: 'stretch',
        overflow: 'hidden',
      }}>
        <div
          ref={mapHostRef}
          style={{
            position: 'relative',
            minHeight: 0,
            minWidth: 0,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            animation: 'fadeUp 0.9s ease both',
          }}
        >
          <MapDecor tricolor={TRICOLOR_WEEKS.has(week)} week={week} />
          <div style={{
            position: 'relative',
            width: '100%',
            height: '100%',
            maxWidth: 720,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}>
            <IndiaMap
              topo={topo}
              width={mapBox.w}
              height={mapBox.h}
              pins={pins}
              week={week}
              hoveredPin={hoveredPin}
              setHoveredPin={setHoveredPin}
              selectedPin={selectedPin}
              setSelectedPin={setSelectedPin}
            />
          </div>
          <MapLegend pinCount={pins.length} weekFestivals={weekFestivals} tricolor={TRICOLOR_WEEKS.has(week)} />
        </div>

        <SidePanel
          weekFestivals={weekFestivals}
          hoveredPin={hoveredPin}
          selectedPin={selectedPin}
          setSelectedPin={setSelectedPin}
          didYouKnow={didYouKnow}
        />
      </main>

      <Timeline
        week={week}
        setWeek={setWeek}
        festivalsByWeek={festivalsByWeek}
      />
    </div>
  );
}

function MapDecor({ tricolor, week }) {
  return (
    <>
      <svg
        style={{
          position: 'absolute',
          inset: 0,
          width: '100%', height: '100%',
          pointerEvents: 'none',
          opacity: 0.5,
        }}
        viewBox="0 0 100 100"
        preserveAspectRatio="none"
      >
        {[[0,0],[100,0],[0,100],[100,100]].map(([x,y], i) => (
          <g key={i} stroke="oklch(0.40 0.02 55)" strokeWidth="0.15" fill="none">
            <line x1={x} y1={y} x2={x + (x===0?6:-6)} y2={y} />
            <line x1={x} y1={y} x2={x} y2={y + (y===0?6:-6)} />
          </g>
        ))}
      </svg>

      {tricolor && (
        <div style={{
          position: 'absolute',
          top: 8, right: 8,
          padding: '6px 12px',
          background: 'rgba(20, 14, 8, 0.78)',
          border: '1px solid rgba(255, 214, 10, 0.35)',
          borderRadius: 3,
          fontFamily: 'Marcellus, serif',
          fontSize: 13,
          letterSpacing: '0.06em',
          color: '#FFD60A',
          pointerEvents: 'none',
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          animation: 'fadeUp 0.6s ease both',
        }}>
          <span style={{
            display: 'inline-block',
            width: 8, height: 8, borderRadius: '50%',
            background: '#FFD60A',
            boxShadow: '0 0 10px #FFD60A',
            animation: 'shimmer 1.6s ease-in-out infinite',
          }} />
          {week === 4 ? 'Republic Day — 26 January' : 'Independence Day — 15 August'}
        </div>
      )}
    </>
  );
}

function MapLegend({ weekFestivals, tricolor }) {
  const stateSet = new Set();
  let pan = false;
  weekFestivals.forEach((f) => {
    if (f.pan) pan = true;
    f.states.forEach((s) => stateSet.add(s));
  });
  if (tricolor) return null;
  return (
    <div style={{
      position: 'absolute',
      bottom: 12, left: 12,
      pointerEvents: 'none',
      display: 'flex',
      flexDirection: 'column',
      gap: 4,
    }}>
      <div className="eyebrow" style={{ fontSize: 9 }}>Lit this week</div>
      <div style={{
        display: 'flex', alignItems: 'baseline', gap: 8,
      }}>
        <span className="t-display" style={{ fontSize: 22, color: 'var(--saffron)' }}>
          {stateSet.size}{pan ? '+' : ''}
        </span>
        <span className="t-serif" style={{
          fontSize: 12,
          fontStyle: 'italic',
          color: 'var(--paper-mute)',
        }}>
          {stateSet.size === 1 ? 'state' : 'states'}{pan ? ', and the whole country' : ''}
        </span>
      </div>
    </div>
  );
}

function LoadingScreen() {
  return (
    <div style={{
      minHeight: '100vh',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      flexDirection: 'column',
      gap: 18,
    }}>
      <div className="t-display" style={{
        fontSize: 36,
        color: 'var(--saffron)',
        animation: 'shimmer 1.4s ease-in-out infinite',
      }}>
        Land of Festival
      </div>
      <div className="t-hindi" style={{
        fontSize: 16,
        color: 'var(--paper-mute)',
        animation: 'shimmer 1.4s ease-in-out infinite',
        animationDelay: '0.2s',
      }}>
        उत्सवों की भूमि …
      </div>
    </div>
  );
}

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