/* App root — Mobile + Fixed (stationary) camera modes, region/type filters,
   day picker (mobile mode), average-speed zone highlights, responsive shell. */
const { useState, useEffect, useMemo, useRef } = React;

// Fixed-camera type styling: hue (oklch), short list tag, and chip description.
// `order` controls the order the type chips appear in.
const ST_TYPE = {
  intersection: { hue: 25,  short: 'Red-light', desc: 'Red-light & speed', order: 0 },
  average:      { hue: 250, short: 'Average',   desc: 'Point-to-point zone', order: 1 },
  phone:        { hue: 165, short: 'Phone',     desc: 'Mobile phone detection', order: 2 },
  pedestrian:   { hue: 70,  short: 'Crossing',  desc: 'Pedestrian crossing', order: 3 },
  rail:         { hue: 310, short: 'Rail',      desc: 'Level (rail) crossing', order: 4 },
};
const ST_FALLBACK = { hue: 200, short: 'Camera', desc: '', order: 9 };

function useMedia(q) {
  const [m, setM] = useState(() => window.matchMedia(q).matches);
  useEffect(() => {
    const mq = window.matchMedia(q);
    const h = () => setM(mq.matches);
    h();
    mq.addEventListener('change', h);
    window.addEventListener('resize', h);
    return () => { mq.removeEventListener('change', h); window.removeEventListener('resize', h); };
  }, [q]);
  return m;
}

function Boot({ error }) {
  return (
    <div className={'boot' + (error ? ' err' : '')}>
      <div className="brand-mark"><span className="brand-pulse"></span><span className="brand-core"></span></div>
      {error ? <div>Couldn’t load camera data.<br/>{error}</div> : <div>Loading today’s cameras…</div>}
    </div>
  );
}

function App() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [mode, setMode] = useState('mobile');            // 'mobile' | 'fixed'
  const [selectedIso, setSelectedIso] = useState(null);
  const [active, setActive] = useState(() => new Set());      // mobile region names
  const [stActive, setStActive] = useState(() => new Set());  // fixed type labels
  const [hoveredId, setHoveredId] = useState(null);
  const [selectedId, setSelectedId] = useState(null);
  const [flyTo, setFlyTo] = useState(null);
  const [fitKey, setFitKey] = useState(0);
  const [sheet, setSheet] = useState(null);          // mobile: null | 'day' | 'filters' | 'list'
  const [mapTheme, setMapTheme] = useState('midnight');
  const isMobile = useMedia('(max-width: 820px)');
  const dayRef = useRef(null);
  const modeRef = useRef(mode);

  // load data once
  useEffect(() => {
    loadData()
      .then((d) => {
        const keys = Object.keys(d.days).sort();
        const today = d.days[d.today] ? d.today : keys[Math.floor(keys.length / 2)];
        setData(d);
        setSelectedIso(today);
        // initialise fixed-camera type filters to all-on
        const st = d.stationary || { types: {}, cameras: [] };
        const labels = new Set((st.cameras || []).map((c) => stLabel(st, c.type)));
        setStActive(labels);
      })
      .catch((e) => setError(String(e.message || e)));
  }, []);

  // ---- MOBILE derived ----
  const availableSet = useMemo(() => new Set(data ? Object.keys(data.days) : []), [data]);
  const day = (data && selectedIso && data.days[selectedIso]) || { regions: [], heading: '' };
  const regions = day.regions;
  const allCams = useMemo(() => {
    const out = [];
    regions.forEach((r) => {
      r.cameras.forEach((c, i) => {
        out.push({
          id: r.name + '::' + c.label + '::' + i,
          road: c.street, suburb: c.suburb, suffix: c.suffix,
          lat: c.lat, lng: c.lng, limit: c.limit,
          region: r.name, hue: r.hue,
        });
      });
    });
    return out;
  }, [data, selectedIso]);
  const counts = useMemo(() => {
    const c = {}; regions.forEach((r) => { c[r.name] = r.cameras.length; }); return c;
  }, [data, selectedIso]);

  // ---- FIXED (stationary) derived ----
  const stationary = (data && data.stationary) || { types: {}, cameras: [], zones: [] };
  const stGroups = useMemo(() => {
    const by = {};
    (stationary.cameras || []).forEach((c) => {
      const t = ST_TYPE[c.type] || ST_FALLBACK;
      const name = stLabel(stationary, c.type);
      if (!by[name]) by[name] = { name, hue: t.hue, desc: t.desc, order: t.order, cameras: [] };
      by[name].cameras.push(c);
    });
    return Object.values(by).sort((a, b) => a.order - b.order);
  }, [data]);
  const stItems = useMemo(() => (stationary.cameras || []).map((c) => {
    const t = ST_TYPE[c.type] || ST_FALLBACK;
    return {
      id: c.id, hue: t.hue, road: c.road, suburb: c.suburb, suffix: '',
      limit: null, tag: t.short, region: stLabel(stationary, c.type), type: c.type,
      lat: c.lat, lng: c.lng,
    };
  }), [data]);
  const stCounts = useMemo(() => {
    const c = {}; stGroups.forEach((g) => { c[g.name] = g.cameras.length; }); return c;
  }, [stGroups]);
  const avgLabel = stLabel(stationary, 'average');

  // ---- current-mode view model ----
  const isFixed = mode === 'fixed';
  const curAll = isFixed ? stItems : allCams;
  const curActive = isFixed ? stActive : active;
  const curCounts = isFixed ? stCounts : counts;
  const curGroups = isFixed
    ? stGroups.map((g) => ({ name: g.name, hue: g.hue, desc: g.desc }))
    : regions.map((r) => ({ name: r.name, hue: r.hue, desc: 'Mobile patrol area' }));
  const visible = useMemo(() => curAll.filter((c) => curActive.has(c.region)), [curAll, curActive]);
  const mappable = useMemo(() => visible.filter((c) => c.lat != null), [visible]);
  const zones = useMemo(() => (
    isFixed && stActive.has(avgLabel) ? (stationary.zones || []) : []
  ), [isFixed, stActive, data]);

  // reset filters + view when the mobile day changes
  useEffect(() => {
    if (!selectedIso || dayRef.current === selectedIso) return;
    dayRef.current = selectedIso;
    setActive(new Set(regions.map((r) => r.name)));
    setFlyTo(null);
    setSelectedId(null);
    setFitKey((k) => k + 1);
  }, [selectedIso, regions]);

  // reset view + refit when the mode switches
  useEffect(() => {
    if (modeRef.current === mode) return;
    modeRef.current = mode;
    setFlyTo(null);
    setSelectedId(null);
    setHoveredId(null);
    setSheet(null);
    setFitKey((k) => k + 1);
  }, [mode]);

  // keep selection valid for the current view
  useEffect(() => {
    if (selectedId && !visible.some((c) => c.id === selectedId)) setSelectedId(null);
  }, [visible, selectedId]);

  if (error) return <Boot error={error} />;
  if (!data || !selectedIso) return <Boot />;

  const todayIso = data.days[data.today] ? data.today : selectedIso;
  const groupNames = curGroups.map((g) => g.name);

  const toggleGroup = (name) => {
    const setter = isFixed ? setStActive : setActive;
    setter((prev) => {
      const next = new Set(prev);
      next.has(name) ? next.delete(name) : next.add(name);
      return next;
    });
  };

  const selectCam = (id) => {
    setSelectedId(id);
    const cam = curAll.find((c) => c.id === id);
    if (cam && cam.lat != null) setFlyTo({ center: [cam.lat, cam.lng], zoom: 15, key: id + '-' + Date.now() });
    if (isMobile) setSheet(null);
  };

  const listKey = mode + ':' + (isFixed ? [...stActive].sort().join('|') : selectedIso + [...active].sort().join('|'));

  const panel = (
    <div className="panel-inner">
      <Brand />
      <ModeToggle mode={mode} onChange={setMode} />
      {!isFixed && (
        <DayPicker valueIso={selectedIso} todayIso={todayIso} availableSet={availableSet}
          onChange={(iso) => setSelectedIso(iso)} />
      )}
      <Filters regions={curGroups} active={curActive} counts={curCounts} onToggle={toggleGroup}
        title={isFixed ? 'Camera types' : 'Regions today'} />
      {!isMobile && (
        <CameraList cameras={visible} hoveredId={hoveredId} selectedId={selectedId}
          onHover={setHoveredId} onSelect={selectCam} dayKey={listKey} />
      )}
    </div>
  );

  const selDate = isoToDate(selectedIso);

  return (
    <div className={'app' + (isMobile ? ' mobile' : '')}>
      {!isMobile && <aside className="panel">{panel}</aside>}

      <main className="stage">
        <MapView cameras={mappable} zones={zones} exact={isFixed}
          hoveredId={hoveredId} selectedId={selectedId}
          onSelect={selectCam} onHover={setHoveredId} theme={mapTheme} flyTo={flyTo} fitKey={fitKey} />

        {!isMobile && (
          <div className="map-style-anchor">
            <MapStyleDropdown value={mapTheme} onChange={setMapTheme} />
          </div>
        )}

        {isMobile && (
          <div className="m-banner">
            <Brand />
            <ModeToggle mode={mode} onChange={setMode} compact />
            <MapStyleDropdown value={mapTheme} onChange={setMapTheme} />
          </div>
        )}

        {isMobile && (
          <div className="m-fabs">
            {!isFixed && (
              <button className="fab quick-day" onClick={() => setSheet('day')}>
                <span className="fab-cap">{selDate.toLocaleDateString('en-AU',{weekday:'short'})}</span>
                <span className="fab-main">{selDate.getDate()}</span>
              </button>
            )}
            <button className={'fab fab-filter' + (curActive.size < groupNames.length ? ' dot' : '')} onClick={() => setSheet('filters')}>
              <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M3 5h18M6 12h12M10 19h4"/></svg>
            </button>
            <button className="fab fab-list" onClick={() => setSheet('list')}>
              <span className="fab-count">{visible.length}</span>
              <span className="fab-lbl">cameras</span>
            </button>
          </div>
        )}
      </main>

      {isMobile && sheet && (
        <div className="sheet-scrim" onClick={() => setSheet(null)}>
          <div className={'sheet sheet-' + sheet} onClick={(e) => e.stopPropagation()}>
            <div className="sheet-grab"></div>
            {sheet === 'day' && (
              <div className="sheet-body">
                <DayPicker valueIso={selectedIso} todayIso={todayIso} availableSet={availableSet}
                  onChange={(iso) => { setSelectedIso(iso); setSheet(null); }} />
              </div>
            )}
            {sheet === 'filters' && (
              <div className="sheet-body">
                <Filters regions={curGroups} active={curActive} counts={curCounts} onToggle={toggleGroup}
                  title={isFixed ? 'Camera types' : 'Regions today'} />
              </div>
            )}
            {sheet === 'list' && (
              <div className="sheet-body">
                <CameraList cameras={visible} hoveredId={hoveredId} selectedId={selectedId}
                  onHover={setHoveredId} onSelect={selectCam} dayKey={listKey} />
              </div>
            )}
          </div>
        </div>
      )}

      <InstallPrompt />
    </div>
  );
}

// resolve a fixed-camera type key to its display label (from data, else the key)
function stLabel(stationary, typeKey) {
  return (stationary.types && stationary.types[typeKey] && stationary.types[typeKey].label) || typeKey;
}

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