// screens-day.jsx — Park Day: the start card, the recording banner, and the
// end-of-day screen where detections get confirmed or thrown away.
//
// The copy in here is load-bearing. This records while the app is on screen
// and not otherwise, and every string a user reads has to say so plainly
// rather than let them believe their phone is quietly following them around
// Frontierland in their pocket. "Paused" is the honest word and it appears
// wherever the recording could be about to stop.
(function () {
const { COLORS, Card, Caption, Display, Body, Icon, PerforatedDivider, Stamp } = window.PH365_UI;

const DAY = () => window.PH365_DAY;

// Dismissed detections. Detection is recomputed from the breadcrumbs every
// time the screen opens — nothing about a rejected guess is written to the
// database — so "no, I didn't ride that" has to be remembered here or the
// same wrong guess comes back every time they look.
const DISMISS_KEY = 'ph365_day_dismissed';
const loadDismissed = () => {
  try { return new Set(JSON.parse(localStorage.getItem(DISMISS_KEY) || '[]')); }
  catch { return new Set(); }
};
const saveDismissed = (set) => {
  try { localStorage.setItem(DISMISS_KEY, JSON.stringify([...set].slice(-500))); } catch {}
};
const dismissKey = (dayId, p) => `${dayId}:${p.attractionId}:${p.start}`;

// ── Formatting ─────────────────────────────────────────────────────────────
const fmtTime = (iso) => new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
const fmtDate = (iso) => new Date(iso).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });

function fmtDuration(ms) {
  const mins = Math.max(0, Math.round(ms / 60_000));
  if (mins < 60) return `${mins}m`;
  return `${Math.floor(mins / 60)}h ${String(mins % 60).padStart(2, '0')}m`;
}

function fmtDistance(m) {
  if (m == null) return '—';
  if (m < 1000) return `${Math.round(m)}m`;
  return `${(m / 1609.34).toFixed(1)} mi`;
}

// ── useDay — subscribe to the tracker ──────────────────────────────────────
function useDay() {
  const [snap, setSnap] = React.useState(() => DAY()?.snapshot?.() || { active: false });
  React.useEffect(() => DAY()?.subscribe?.(setSnap), []);
  return snap;
}

// ── Home card ──────────────────────────────────────────────────────────────
function ParkDayHomeCard() {
  const snap = useDay();
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const USER = window.PH365.USER || {};

  const open = () => window.dispatchEvent(new CustomEvent('ph365-open-park-day'));

  const start = async () => {
    setBusy(true); setErr(null);
    try { await DAY().startDay(); }
    catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  if (snap.active) {
    return (
      <Card onClick={open} style={{ padding: 14, cursor: 'pointer', borderColor: COLORS.moss }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <RecordingDot />
          <div style={{ flex: 1, minWidth: 0 }}>
            <Body size={14} weight={700}>Park day in progress</Body>
            <Caption style={{ marginTop: 2 }}>
              Started {fmtTime(snap.startedAt)} · {fmtDuration(Date.now() - new Date(snap.startedAt).getTime())}
            </Caption>
          </div>
          <Icon name="chevron" size={14} color={COLORS.inkSoft} />
        </div>
      </Card>
    );
  }

  return (
    <Card style={{ padding: 14 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <Icon name="navigate" size={18} color={COLORS.teal} />
        <div style={{ flex: 1 }}>
          <Body size={14} weight={700}>Start my day</Body>
          <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 4, lineHeight: 1.5 }}>
            Traces your path through the park and works out which rides you went on,
            so you get a map of the day at the end of it.
          </Body>
          <Body size={11.5} color={COLORS.inkSoft} style={{ marginTop: 6, lineHeight: 1.45 }}>
            Recording runs while this app is open. Lock the phone or switch apps and it
            pauses until you come back — iPhone gives web apps no location in the
            background, and this one doesn't pretend otherwise.
          </Body>

          {USER.isGuest ? (
            <Caption color={COLORS.clay} style={{ marginTop: 10 }}>
              Sign in to record a park day.
            </Caption>
          ) : (
            <button
              onClick={start}
              disabled={busy}
              style={{
                marginTop: 12, width: '100%', padding: '11px 14px',
                background: busy ? COLORS.paperDeep : COLORS.teal,
                color: busy ? COLORS.inkSoft : '#fff',
                border: 'none', borderRadius: 10, cursor: busy ? 'default' : 'pointer',
                fontFamily: 'Manrope, sans-serif', fontSize: 14, fontWeight: 700,
              }}
            >
              {busy ? 'Getting a fix…' : 'Start my day'}
            </button>
          )}
          {err && <Caption color={COLORS.clay} style={{ marginTop: 8 }}>{err}</Caption>}
          <button
            onClick={open}
            style={{
              marginTop: 10, background: 'transparent', border: 'none', padding: 0,
              cursor: 'pointer', fontFamily: 'Manrope, sans-serif',
              fontSize: 12, color: COLORS.inkSoft, textDecoration: 'underline',
            }}
          >
            Past days &amp; location history
          </button>
        </div>
      </div>
    </Card>
  );
}

function RecordingDot() {
  return (
    <>
      <style>{`@keyframes ph365-rec { 0%,100% { opacity: 1 } 50% { opacity: 0.25 } }`}</style>
      <div style={{
        width: 10, height: 10, borderRadius: '50%', background: COLORS.moss,
        animation: 'ph365-rec 1.8s ease-in-out infinite', flexShrink: 0,
      }} />
    </>
  );
}

// ── Recording banner ───────────────────────────────────────────────────────
// Rendered at the App level so it survives tab changes, which is also the
// point of the tracker living on a module: switching to the Map tab should
// not end someone's day.
function ParkDayBanner() {
  const snap = useDay();
  const [, tick] = React.useReducer(x => x + 1, 0);
  const [ending, setEnding] = React.useState(false);

  React.useEffect(() => {
    if (!snap.active) return;
    const id = setInterval(tick, 20_000);
    return () => clearInterval(id);
  }, [snap.active]);

  if (!snap.active) return null;

  const elapsed = fmtDuration(Date.now() - new Date(snap.startedAt).getTime());
  const paused = document.hidden;

  const end = async (e) => {
    e.stopPropagation();
    setEnding(true);
    try {
      const closed = await DAY().endDay({ endReason: 'ended' });
      window.dispatchEvent(new CustomEvent('ph365-open-park-day', { detail: { dayId: closed?.id } }));
    } catch (err) {
      console.warn('[park-day] end failed', err);
    } finally {
      setEnding(false);
    }
  };

  return (
    <div
      onClick={() => window.dispatchEvent(new CustomEvent('ph365-open-park-day'))}
      style={{
        // A flex item in the app's column, not an overlay. Overlaying it
        // would sit on top of every screen's own header; taking the space
        // costs 44px and never covers anything up.
        flexShrink: 0, margin: '6px 10px 0',
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '8px 12px', borderRadius: 12,
        background: COLORS.paperDeep,
        border: `1px solid ${COLORS.paperEdge}`,
        boxShadow: '0 6px 18px rgba(26,37,102,0.10)',
        cursor: 'pointer',
      }}
    >
      <RecordingDot />
      <div style={{ flex: 1, minWidth: 0 }}>
        <Body size={12.5} weight={700}>
          {paused ? 'Park day paused' : 'Recording your day'} · {elapsed}
        </Body>
        <Caption style={{ marginTop: 1, fontSize: 10 }}>
          {snap.lastError
            ? snap.lastError
            : `${snap.sampleCount + snap.pendingCount} points · pauses when you leave the app`}
        </Caption>
      </div>
      <button
        onClick={end}
        disabled={ending}
        style={{
          background: COLORS.teal, color: '#fff', border: 'none',
          borderRadius: 8, padding: '6px 10px', cursor: 'pointer',
          fontFamily: 'Manrope, sans-serif', fontSize: 11.5, fontWeight: 700,
          flexShrink: 0,
        }}
      >
        {ending ? 'Ending…' : 'End day'}
      </button>
    </div>
  );
}

// ── The map ────────────────────────────────────────────────────────────────
// Same MapLibre + lazy-CDN-load pattern as screen-map.jsx. The path is drawn
// as one feature per segment rather than a single line: a straight run from
// the last fix before a gap to the first one after it is not a walk that
// happened, and drawing it as one would be inventing the most interesting
// part of the day.
function DayMap({ segments, rides = [], height = 240 }) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const [ready, setReady] = React.useState(!!window.maplibregl);

  React.useEffect(() => {
    if (window.maplibregl) { setReady(true); return; }
    if (!document.querySelector('link[data-maplibre]')) {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = 'https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css';
      link.dataset.maplibre = 'true';
      document.head.appendChild(link);
    }
    if (!document.querySelector('script[data-maplibre]')) {
      const s = document.createElement('script');
      s.src = 'https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js';
      s.dataset.maplibre = 'true';
      s.onload = () => setReady(true);
      s.onerror = () => console.error('[park-day] MapLibre CDN load failed');
      document.head.appendChild(s);
    } else {
      const wait = setInterval(() => {
        if (window.maplibregl) { clearInterval(wait); setReady(true); }
      }, 100);
      return () => clearInterval(wait);
    }
  }, []);

  const points = React.useMemo(() => segments.flat(), [segments]);

  React.useEffect(() => {
    if (!ready || !containerRef.current || mapRef.current || !points.length) return;
    const ml = window.maplibregl;
    const cfg = window.__PH365_CONFIG__ || {};
    const style = cfg.stadiaApiKey
      ? `https://tiles.stadiamaps.com/styles/alidade_bright.json?api_key=${encodeURIComponent(cfg.stadiaApiKey)}`
      : 'https://tiles.openfreemap.org/styles/bright';

    const map = new ml.Map({
      container: containerRef.current,
      style,
      center: [points[0].lon, points[0].lat],
      zoom: 15.5,
      attributionControl: { compact: true },
      interactive: true,
    });
    mapRef.current = map;

    map.on('load', () => {
      map.addSource('day-path', {
        type: 'geojson',
        data: {
          type: 'FeatureCollection',
          features: segments.map(seg => ({
            type: 'Feature',
            properties: {},
            geometry: { type: 'LineString', coordinates: seg.map(s => [s.lon, s.lat]) },
          })),
        },
      });
      map.addLayer({
        id: 'day-path-glow', type: 'line', source: 'day-path',
        layout: { 'line-cap': 'round', 'line-join': 'round' },
        paint: { 'line-color': COLORS.teal, 'line-width': 9, 'line-opacity': 0.18 },
      });
      map.addLayer({
        id: 'day-path-line', type: 'line', source: 'day-path',
        layout: { 'line-cap': 'round', 'line-join': 'round' },
        paint: { 'line-color': COLORS.teal, 'line-width': 3 },
      });

      const bounds = points.reduce(
        (b, p) => b.extend([p.lon, p.lat]),
        new ml.LngLatBounds([points[0].lon, points[0].lat], [points[0].lon, points[0].lat])
      );
      map.fitBounds(bounds, { padding: 36, maxZoom: 17, duration: 0 });

      const dot = (color) => {
        const el = document.createElement('div');
        el.style.cssText = `width:12px;height:12px;border-radius:50%;background:${color};`
          + 'border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,0.3);';
        return el;
      };
      new ml.Marker({ element: dot(COLORS.mustard) })
        .setLngLat([points[0].lon, points[0].lat]).addTo(map);
      new ml.Marker({ element: dot(COLORS.moss) })
        .setLngLat([points[points.length - 1].lon, points[points.length - 1].lat]).addTo(map);
    });

    return () => { map.remove(); mapRef.current = null; };
  }, [ready, points, segments]);

  if (!points.length) {
    return (
      <div style={{
        height, borderRadius: 12, background: COLORS.paperDeep,
        border: `1px solid ${COLORS.paperEdge}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
      }}>
        <Caption style={{ textAlign: 'center', lineHeight: 1.5 }}>
          No path to draw — every fix was too rough to plot, or the app wasn't open long enough.
        </Caption>
      </div>
    );
  }

  return (
    <div
      ref={containerRef}
      style={{
        height, borderRadius: 12, overflow: 'hidden',
        border: `1px solid ${COLORS.paperEdge}`,
      }}
    />
  );
}

// ── Day detail / end-of-day ────────────────────────────────────────────────
function ParkDayDetail({ dayId, onBack }) {
  const [day, setDay] = React.useState(null);
  const [proposals, setProposals] = React.useState([]);
  const [error, setError] = React.useState(null);
  const [dismissed, setDismissed] = React.useState(loadDismissed);
  const [working, setWorking] = React.useState(null);
  const [confirmDelete, setConfirmDelete] = React.useState(false);

  const attractionsById = React.useMemo(() => {
    const m = new Map();
    for (const a of (window.PH365.ATTRACTIONS || [])) m.set(a.id, a);
    return m;
  }, []);

  const load = React.useCallback(async () => {
    try {
      const d = await DAY().getDay(dayId);
      setDay(d);
      const found = await DAY().detectRides({ samples: d.samples, visits: d.visits });
      setProposals(found);
    } catch (e) {
      setError(e.message);
    }
  }, [dayId]);

  React.useEffect(() => { load(); }, [load]);

  const keep = async (p) => {
    setWorking(p.start + p.attractionId);
    try {
      await window.PH365_ACTIONS.checkIn({
        attractionId: p.attractionId,
        source: 'gps',
        dayId,
        visitedAt: p.ridenAt,
      });
      await load();
    } catch (e) {
      setError(e.message);
    } finally {
      setWorking(null);
    }
  };

  const dismiss = (p) => {
    const next = new Set(dismissed);
    next.add(dismissKey(dayId, p));
    setDismissed(next);
    saveDismissed(next);
  };

  const remove = async () => {
    try {
      await DAY().deleteDay(dayId);
      onBack();
    } catch (e) {
      setError(e.message);
    }
  };

  if (error && !day) {
    return (
      <ScreenShell title="Park day" onBack={onBack}>
        <Card style={{ padding: 14 }}>
          <Caption color={COLORS.clay}>Couldn't load this day</Caption>
          <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 4 }}>{error}</Body>
        </Card>
      </ScreenShell>
    );
  }
  if (!day) {
    return (
      <ScreenShell title="Park day" onBack={onBack}>
        <Caption style={{ textAlign: 'center', padding: '24px 0' }}>Loading your day…</Caption>
      </ScreenShell>
    );
  }

  const segments = DAY().pathSegments(day.samples);
  const open = !day.ended_at;
  const elapsed = (day.ended_at ? new Date(day.ended_at) : new Date()) - new Date(day.started_at);
  const live = proposals.filter(p => !dismissed.has(dismissKey(dayId, p)));

  // Confirmed rides and still-unconfirmed guesses, interleaved into one
  // timeline — which is the thing the user actually wants to look at.
  const timeline = [
    ...day.visits.map(v => ({
      key: `v-${v.id}`, at: v.visited_at, confirmed: true,
      name: attractionsById.get(v.attraction_id)?.name || v.attraction_id,
      source: v.source,
    })),
    ...live.map(p => ({ key: `p-${p.start}-${p.attractionId}`, at: p.ridenAt, confirmed: false, proposal: p })),
  ].sort((a, b) => new Date(a.at) - new Date(b.at));

  return (
    <ScreenShell
      title={fmtDate(day.started_at)}
      subtitle={`${day.park_id || 'Park'} · ${fmtTime(day.started_at)}${day.ended_at ? ` – ${fmtTime(day.ended_at)}` : ' · still recording'}`}
      onBack={onBack}
    >
      <DayMap segments={segments} />

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, margin: '14px 0 4px' }}>
        <StatCell label="In park" value={fmtDuration(elapsed)} />
        <StatCell label="Walked" value={fmtDistance(day.distance_m)} />
        <StatCell label="Rides" value={day.visits.length} />
      </div>
      <Caption style={{ fontSize: 10, lineHeight: 1.5, marginBottom: 4 }}>
        {day.gap_count > 0
          ? `${day.gap_count} gap${day.gap_count === 1 ? '' : 's'} where the app was closed — distance walked doesn't count those, so it's an undercount.`
          : 'Distance covers the stretches the app was open for.'}
        {day.median_accuracy_m != null && ` Typical fix ±${day.median_accuracy_m}m.`}
      </Caption>

      {live.length > 0 && (
        <>
          <PerforatedDivider label={`${live.length} ride${live.length === 1 ? '' : 's'} to confirm`} />
          <Caption style={{ margin: '10px 0 10px', lineHeight: 1.5 }}>
            Guesses from where you stopped and when the app went away. Nothing goes
            into your history until you keep it.
          </Caption>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
            {live.map(p => (
              <ProposalCard
                key={`${p.start}-${p.attractionId}`}
                proposal={p}
                busy={working === p.start + p.attractionId}
                onKeep={() => keep(p)}
                onDismiss={() => dismiss(p)}
              />
            ))}
          </div>
        </>
      )}

      <PerforatedDivider label="The day" />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, margin: '12px 0 18px' }}>
        {timeline.length === 0 && (
          <Card style={{ padding: 14, textAlign: 'center' }}>
            <Caption>Nothing logged yet</Caption>
            <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 6, lineHeight: 1.5 }}>
              {open
                ? 'Keep the app open as you go and rides will start showing up here.'
                : 'No rides confirmed, and nothing detected from the path.'}
            </Body>
          </Card>
        )}
        {timeline.map(t => (
          <div key={t.key} style={{
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '9px 12px', borderRadius: 10,
            background: t.confirmed ? COLORS.paperDeep : 'transparent',
            border: `1px ${t.confirmed ? 'solid' : 'dashed'} ${COLORS.paperEdge}`,
          }}>
            <Caption style={{ width: 62, flexShrink: 0 }}>{fmtTime(t.at)}</Caption>
            <Body size={13} weight={t.confirmed ? 700 : 500} style={{
              flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
              color: t.confirmed ? COLORS.ink : COLORS.inkSoft,
            }}>
              {t.confirmed ? t.name : t.proposal.name}
            </Body>
            {t.confirmed
              ? <Icon name="check" size={14} color={COLORS.moss} />
              : <Caption style={{ fontSize: 10 }}>unconfirmed</Caption>}
          </div>
        ))}
      </div>

      <PerforatedDivider label="This day's location data" />
      <Card style={{ padding: 14, marginTop: 12, marginBottom: 24 }}>
        <Body size={12} color={COLORS.inkSoft} style={{ lineHeight: 1.5 }}>
          {day.sample_count} location points, visible only to you. Deleting them removes
          the path and this map. Rides you kept stay in your ride history.
        </Body>
        {confirmDelete ? (
          <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
            <button onClick={remove} style={dangerBtn}>Delete this day's path</button>
            <button onClick={() => setConfirmDelete(false)} style={quietBtn}>Cancel</button>
          </div>
        ) : (
          <button onClick={() => setConfirmDelete(true)} style={{ ...quietBtn, marginTop: 12 }}>
            <Icon name="trash" size={13} color={COLORS.clay} />
            <span style={{ marginLeft: 6, color: COLORS.clay }}>Delete this day's path</span>
          </button>
        )}
      </Card>
      {error && <Caption color={COLORS.clay} style={{ marginBottom: 20 }}>{error}</Caption>}
    </ScreenShell>
  );
}

function ProposalCard({ proposal, busy, onKeep, onDismiss }) {
  const confColor = proposal.confidence === 'high' ? COLORS.moss
                  : proposal.confidence === 'medium' ? COLORS.mustard
                  : COLORS.inkSoft;
  return (
    <Card style={{ padding: 12 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <div style={{
          width: 8, height: 8, borderRadius: '50%', background: confColor,
          marginTop: 5, flexShrink: 0,
        }} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <Body size={14} weight={700}>{proposal.name}</Body>
          {/* ridenAt, not the window start: this is the time the ride will be
              logged at if they keep it, and it is also what the timeline below
              shows. Two different times for the same ride on one screen reads
              as a bug even when both are defensible. */}
          <Caption style={{ marginTop: 2 }}>
            {fmtTime(proposal.ridenAt)} · {proposal.minutes}m here · {proposal.basis}
          </Caption>
          {!proposal.anchored && (
            <Caption style={{ marginTop: 2, fontSize: 10 }}>
              Matched to the ride's map pin, not a surveyed entrance — nearby rides can look alike.
            </Caption>
          )}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
        <button onClick={onKeep} disabled={busy} style={{
          flex: 1, padding: '8px 10px', borderRadius: 9, border: 'none',
          background: busy ? COLORS.paperDeep : COLORS.teal,
          color: busy ? COLORS.inkSoft : '#fff', cursor: busy ? 'default' : 'pointer',
          fontFamily: 'Manrope, sans-serif', fontSize: 12.5, fontWeight: 700,
        }}>{busy ? 'Saving…' : 'I rode this'}</button>
        <button onClick={onDismiss} disabled={busy} style={{
          flex: 1, padding: '8px 10px', borderRadius: 9,
          border: `1px solid ${COLORS.paperEdge}`, background: 'transparent',
          color: COLORS.inkSoft, cursor: 'pointer',
          fontFamily: 'Manrope, sans-serif', fontSize: 12.5, fontWeight: 600,
        }}>Didn't ride it</button>
      </div>
    </Card>
  );
}

// ── Day list + the global delete ───────────────────────────────────────────
function ParkDayScreen({ initialDayId = null, onBack }) {
  const [selected, setSelected] = React.useState(initialDayId);
  const [days, setDays] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [confirmWipe, setConfirmWipe] = React.useState(false);
  const snap = useDay();

  const load = React.useCallback(async () => {
    try { setDays(await DAY().listDays()); }
    catch (e) { setError(e.message); setDays([]); }
  }, []);

  React.useEffect(() => { if (!selected) load(); }, [selected, load]);

  if (selected) {
    return <ParkDayDetail dayId={selected} onBack={() => {
      setSelected(null);
      if (initialDayId) onBack();
    }} />;
  }

  const wipe = async () => {
    try {
      await DAY().deleteAllHistory();
      setConfirmWipe(false);
      load();
    } catch (e) { setError(e.message); }
  };

  return (
    <ScreenShell title="Park days" subtitle="Your recorded days, and what happens to them." onBack={onBack}>
      {snap.active && (
        <Card onClick={() => setSelected(snap.dayId)} style={{
          padding: 14, marginBottom: 12, cursor: 'pointer', borderColor: COLORS.moss,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <RecordingDot />
            <div style={{ flex: 1 }}>
              <Body size={14} weight={700}>Today, in progress</Body>
              <Caption style={{ marginTop: 2 }}>
                {fmtDuration(Date.now() - new Date(snap.startedAt).getTime())} ·
                {' '}{snap.sampleCount + snap.pendingCount} points
              </Caption>
            </div>
            <Icon name="chevron" size={14} color={COLORS.inkSoft} />
          </div>
        </Card>
      )}

      {days === null && <Caption style={{ textAlign: 'center', padding: '24px 0' }}>Loading…</Caption>}
      {days?.length === 0 && !snap.active && (
        <Card style={{ padding: 14, textAlign: 'center' }}>
          <Caption>No park days yet</Caption>
          <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 6, lineHeight: 1.5 }}>
            Tap "Start my day" on Today when you get to the park.
          </Body>
        </Card>
      )}

      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {(days || []).filter(d => d.ended_at).map(d => (
          <Card key={d.id} onClick={() => setSelected(d.id)} style={{ padding: 13, cursor: 'pointer' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <Body size={14} weight={700}>{fmtDate(d.started_at)}</Body>
                <Caption style={{ marginTop: 2 }}>
                  {d.park_id || 'Park'} · {fmtTime(d.started_at)} – {fmtTime(d.ended_at)}
                </Caption>
              </div>
              <Icon name="chevron" size={14} color={COLORS.inkSoft} />
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6, marginTop: 10 }}>
              <StatCell label="Rides" value={d.ride_count} small />
              <StatCell label="Walked" value={fmtDistance(d.distance_m)} small />
              <StatCell label="Points" value={d.sample_count} small />
            </div>
          </Card>
        ))}
      </div>

      <PerforatedDivider label="Privacy" />
      <Card style={{ padding: 14, margin: '12px 0 30px' }}>
        <Body size={12} color={COLORS.inkSoft} style={{ lineHeight: 1.55 }}>
          Your park days are stored against your account and no one else can read them —
          not other passholders, not an admin. Recording only ever happens while this app
          is open on your screen; nothing is collected in the background, because on
          iPhone a web app simply can't.
        </Body>
        {confirmWipe ? (
          <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
            <button onClick={wipe} style={dangerBtn}>Yes, delete everything</button>
            <button onClick={() => setConfirmWipe(false)} style={quietBtn}>Cancel</button>
          </div>
        ) : (
          <button onClick={() => setConfirmWipe(true)} style={{ ...quietBtn, marginTop: 12 }}>
            <Icon name="trash" size={13} color={COLORS.clay} />
            <span style={{ marginLeft: 6, color: COLORS.clay }}>Delete all location history</span>
          </button>
        )}
        <Caption style={{ marginTop: 8, fontSize: 10, lineHeight: 1.5 }}>
          Deletes every path and every location point, for every day. Rides you confirmed
          stay in your ride history and your achievements are untouched.
        </Caption>
      </Card>
      {error && <Caption color={COLORS.clay}>{error}</Caption>}
    </ScreenShell>
  );
}

// ── Shared bits ────────────────────────────────────────────────────────────
function ScreenShell({ title, subtitle, onBack, children }) {
  return (
    <div style={{ paddingBottom: 100 }}>
      <div style={{ padding: '8px 16px 16px', borderBottom: `1px dashed ${COLORS.paperEdge}` }}>
        <button onClick={onBack} style={{
          background: 'transparent', border: 'none', padding: 0, cursor: 'pointer',
          display: 'flex', alignItems: 'center', gap: 4, marginBottom: 14,
          fontFamily: 'Manrope, sans-serif', fontSize: 13, color: COLORS.inkSoft,
        }}>
          <span style={{ display: 'inline-flex', transform: 'scaleX(-1)' }}>
            <Icon name="chevron" size={14} color={COLORS.inkSoft} />
          </span>
          Back
        </button>
        <Display size={26}>{title}</Display>
        {subtitle && <Caption style={{ marginTop: 6 }}>{subtitle}</Caption>}
      </div>
      <div style={{ padding: '14px 16px' }}>{children}</div>
    </div>
  );
}

function StatCell({ label, value, small = false }) {
  return (
    <div style={{
      background: COLORS.paperDeep, border: `1px solid ${COLORS.paperEdge}`,
      borderRadius: 9, padding: small ? '6px 8px' : '9px 10px', textAlign: 'center',
    }}>
      <Body size={small ? 12 : 15} weight={700}>{value}</Body>
      <Caption style={{ fontSize: 9, marginTop: 1 }}>{label}</Caption>
    </div>
  );
}

const dangerBtn = {
  flex: 1, padding: '9px 12px', borderRadius: 9, border: 'none',
  background: COLORS.clay, color: '#fff', cursor: 'pointer',
  fontFamily: 'Manrope, sans-serif', fontSize: 12.5, fontWeight: 700,
};

const quietBtn = {
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
  padding: '9px 12px', borderRadius: 9,
  border: `1px solid ${COLORS.paperEdge}`, background: 'transparent',
  cursor: 'pointer', fontFamily: 'Manrope, sans-serif', fontSize: 12.5, fontWeight: 600,
  color: COLORS.inkSoft,
};

Object.assign(window, { ParkDayHomeCard, ParkDayBanner, ParkDayScreen });
})();
