// App root — sticky header (date + tab bar), data fetching, push registration.

const { api, buildSeries, TOKEN } = window.HC_DATA;
const SeriesContext = window.SeriesContext;

function PushBell({ theme }) {
  const p = theme.palette;
  const [state, setState] = React.useState('checking');
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    pushStatus().then(setState).catch(() => setState('off'));
  }, []);

  async function tap() {
    if (state === 'on' || state === 'blocked' || state === 'unsupported' || state === 'enabling') return;
    setErr(null); setState('enabling');
    try {
      await enablePush();
      setState('on');
    } catch (e) {
      setErr(e.message || 'failed');
      setState(Notification && Notification.permission === 'denied' ? 'blocked' : 'off');
    }
  }

  // Don't render if push isn't supported on this device at all.
  if (state === 'unsupported') return null;

  const label = state === 'on' ? 'on'
              : state === 'enabling' ? '…'
              : state === 'blocked' ? 'blocked'
              : 'enable push';
  const color = state === 'on' ? p.training
              : state === 'blocked' ? p.warning
              : p.accent;

  return (
    <button onClick={tap} title={err || ''} style={{
      background: 'transparent', border: 'none', padding: 0,
      fontFamily: 'DM Mono, monospace', fontSize: 11, color,
      cursor: (state === 'off' || state === 'enabling') ? 'pointer' : 'default',
    }}>
      🔔 {label}
    </button>
  );
}

function TabBar({ active, onChange, theme }) {
  const p = theme.palette;
  const tabs = [['today', 'today'], ['log', 'log'], ['trends', 'trends'], ['ask', 'ask']];
  return (
    <div style={{
      display: 'flex', gap: 4, padding: 3,
      background: p.tabRail, borderRadius: 12, margin: '0 16px',
    }}>
      {tabs.map(([k, label]) => (
        <button key={k} onClick={() => onChange(k)} style={{
          flex: 1, padding: '10px 0', border: 'none', borderRadius: 9,
          fontFamily: 'DM Sans', fontSize: 13, letterSpacing: 0.1,
          background: active === k ? p.tabActive : 'transparent',
          color: active === k ? p.text : p.text2,
          boxShadow: active === k ? '0 1px 3px rgba(0,0,0,0.06)' : 'none',
          fontWeight: active === k ? 500 : 400, cursor: 'pointer',
        }}>{label}</button>
      ))}
    </div>
  );
}

function fmtHeaderDate(iso, dayName, dayType) {
  if (!iso) return { left: '—', right: '' };
  const dt = new Date(iso + 'T00:00:00');
  const months = ['January','February','March','April','May','June',
                  'July','August','September','October','November','December'];
  const left = `${dayName || ''} ${months[dt.getMonth()]} ${dt.getDate()}`.trim();
  const rightMap = {
    wfh: 'wfh day', office: 'office day', rest: 'rest day',
    saturday: 'saturday', sunday: 'sunday',
  };
  const right = rightMap[dayType] || '';
  return { left, right };
}

// ── Push registration ──────────────────────────────────────────────
// iOS Safari requires a user gesture to trigger Notification.requestPermission().
// Calling it on app load silently fails. We register the service worker on
// load so it's installed, but defer subscription until the user taps a button.

async function ensureServiceWorker() {
  if (!('serviceWorker' in navigator)) return null;
  try { return await navigator.serviceWorker.register('/sw.js'); }
  catch (e) { console.warn('sw register failed:', e); return null; }
}

async function enablePush() {
  const reg = await ensureServiceWorker();
  if (!reg) throw new Error('service worker unsupported');
  if (!('PushManager' in window)) throw new Error('push unsupported');
  if (!('Notification' in window)) throw new Error('notifications unsupported');

  if (Notification.permission === 'default') {
    const perm = await Notification.requestPermission();
    if (perm !== 'granted') throw new Error('permission denied');
  } else if (Notification.permission === 'denied') {
    throw new Error('permission denied');
  }

  const keyRes = await api('/api/vapid-public-key');
  if (!keyRes || !keyRes.key) throw new Error('no vapid key');

  let sub = await reg.pushManager.getSubscription();
  if (!sub) {
    sub = await reg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(keyRes.key),
    });
  }
  await api('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub.toJSON()) });
  return sub;
}

async function pushStatus() {
  if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) {
    return 'unsupported';
  }
  if (Notification.permission === 'denied') return 'blocked';
  if (Notification.permission !== 'granted') return 'off';
  try {
    const reg = await navigator.serviceWorker.getRegistration('/sw.js');
    if (!reg) return 'off';
    const sub = await reg.pushManager.getSubscription();
    return sub ? 'on' : 'off';
  } catch (e) { return 'off'; }
}

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const raw = atob(base64);
  return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)));
}

// ── App ────────────────────────────────────────────────────────────

function MissingToken({ theme }) {
  const p = theme.palette;
  const [input, setInput] = React.useState('');

  function save() {
    let t = input.trim();
    if (!t) return;
    // Accept full URL or just the token string.
    try {
      const u = new URL(t);
      const tok = u.searchParams.get('token');
      if (tok) t = tok;
    } catch (e) { /* not a URL, use as-is */ }
    window.HC_DATA.saveToken(t);
  }

  return (
    <div style={{
      padding: '80px 24px 24px', color: p.text,
      fontFamily: 'DM Sans', fontSize: 14, maxWidth: 360, margin: '0 auto',
    }}>
      <div style={{ fontSize: 18, fontWeight: 600, marginBottom: 10, textAlign: 'center' }}>
        Token needed
      </div>
      <div style={{ color: p.text2, fontSize: 13, lineHeight: 1.45, marginBottom: 18, textAlign: 'center' }}>
        Paste your install URL or the token below. Saved to this device only.
      </div>
      <textarea value={input} onChange={(e) => setInput(e.target.value)}
                placeholder="https://app.valmun.dev/?token=…"
                rows={3} style={{
                  width: '100%', boxSizing: 'border-box', padding: 12,
                  fontFamily: 'DM Mono, monospace', fontSize: 12, color: p.text,
                  border: '1px solid ' + p.border, borderRadius: 8,
                  resize: 'none', outline: 'none', background: p.inputBg,
                }} />
      <button onClick={save} disabled={!input.trim()} style={{
        marginTop: 12, width: '100%', padding: '12px 0',
        fontFamily: 'DM Sans', fontSize: 14, fontWeight: 500,
        background: input.trim() ? p.accent : p.border,
        color: '#fff', border: 'none', borderRadius: 8,
        cursor: input.trim() ? 'pointer' : 'default',
      }}>save</button>
    </div>
  );
}

function Loading({ theme }) {
  const p = theme.palette;
  return (
    <div style={{
      padding: '80px 16px', textAlign: 'center',
      color: p.text3, fontFamily: 'DM Mono, monospace', fontSize: 12,
    }}>loading…</div>
  );
}

function ErrorView({ err, theme }) {
  const p = theme.palette;
  return (
    <div style={{ padding: '60px 24px', fontFamily: 'DM Sans', textAlign: 'center' }}>
      <div style={{ color: p.warning, fontWeight: 600, marginBottom: 8 }}>Couldn't load</div>
      <div style={{ color: p.text2, fontSize: 13, fontFamily: 'DM Mono, monospace' }}>
        {err && err.detail ? JSON.stringify(err.detail) : (err && err.message) || 'unknown error'}
      </div>
    </div>
  );
}

function App() {
  const theme = window.HC_THEME.THEME;
  const p = theme.palette;
  const [tab, setTab] = React.useState('today');
  const [todayData, setTodayData] = React.useState(null);
  const [trendsData, setTrendsData] = React.useState(null);
  const [err, setErr] = React.useState(null);

  const refreshToday = React.useCallback(async () => {
    try {
      const d = await api('/api/today');
      setTodayData(d);
      return d;
    } catch (e) { setErr(e); throw e; }
  }, []);

  React.useEffect(() => {
    if (!TOKEN) return;
    let alive = true;
    (async () => {
      try {
        const [today, trends] = await Promise.all([
          api('/api/today'),
          api('/api/sleep-trends?months=5'),
        ]);
        if (!alive) return;
        setTodayData(today);
        setTrendsData(trends);
      } catch (e) {
        if (alive) setErr(e);
      }
    })();
    // Register SW eagerly (doesn't prompt), but defer push subscribe until
    // user taps the bell — iOS Safari requires a user gesture.
    ensureServiceWorker();
    return () => { alive = false; };
  }, []);

  // Auto-refresh /api/today every 60s while on Today tab.
  React.useEffect(() => {
    if (!TOKEN || tab !== 'today') return;
    const id = setInterval(() => { refreshToday().catch(() => {}); }, 60000);
    return () => clearInterval(id);
  }, [tab, refreshToday]);

  // Refresh /api/today when the app regains focus (returning from background).
  React.useEffect(() => {
    if (!TOKEN) return;
    function onVis() { if (!document.hidden) refreshToday().catch(() => {}); }
    document.addEventListener('visibilitychange', onVis);
    return () => document.removeEventListener('visibilitychange', onVis);
  }, [refreshToday]);

  if (!TOKEN) {
    return (
      <window.HC_THEME.ThemeContext.Provider value={theme}>
        <MissingToken theme={theme} />
      </window.HC_THEME.ThemeContext.Provider>
    );
  }

  const header = fmtHeaderDate(
    todayData && todayData.day,
    todayData && todayData.day_name,
    todayData && todayData.day_type,
  );

  // Series for sparklines/charts: prefer the rich /api/sleep-trends data.
  const series = React.useMemo(() => trendsData ? buildSeries(trendsData.days) : null, [trendsData]);

  let body;
  if (err && !todayData) {
    body = <ErrorView err={err} theme={theme} />;
  } else if (!todayData) {
    body = <Loading theme={theme} />;
  } else if (tab === 'today') {
    body = <window.TodayTab today={todayData} />;
  } else if (tab === 'log') {
    body = <window.LogTab today={todayData} onUpdate={refreshToday} />;
  } else if (tab === 'trends') {
    body = <window.TrendsTab trends={trendsData} series={series} />;
  } else {
    body = <window.AskTab />;
  }

  return (
    <window.HC_THEME.ThemeContext.Provider value={theme}>
      <SeriesContext.Provider value={series}>
        <div style={{ background: p.bg, minHeight: '100vh' }}>
          <div style={{
            position: 'sticky', top: 0, zIndex: 5,
            background: 'rgba(247,246,243,0.82)',
            backdropFilter: 'blur(14px) saturate(160%)',
            WebkitBackdropFilter: 'blur(14px) saturate(160%)',
            borderBottom: `1px solid ${p.border}`,
            paddingTop: 'env(safe-area-inset-top, 0)',
            paddingBottom: 12,
          }}>
            <div style={{
              display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
              padding: '14px 20px 14px',
            }}>
              <div style={{
                fontFamily: 'DM Sans', fontSize: 19, fontWeight: 600,
                color: p.text, letterSpacing: -0.2,
              }}>{header.left}</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                <PushBell theme={theme} />
                <div style={{ fontFamily: 'DM Sans', fontSize: 13, color: p.text3 }}>{header.right}</div>
              </div>
            </div>
            <TabBar active={tab} onChange={setTab} theme={theme} />
          </div>
          <div style={{ height: 16 }} />
          {body}
        </div>
      </SeriesContext.Provider>
    </window.HC_THEME.ThemeContext.Provider>
  );
}

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