// SideJobScreen.jsx — iPad Side Jobs: a "waterfall" list of recurring
// cleaning / maintenance items crew clear one by one (v11.11 rework — the old
// floor-plan heat map + PIN flow are gone).
//
// Each item (side_job_tasks) has a cadence (cycle_days), points, and rich-text
// instructions. The list is sorted most-urgent first; every row's BACKGROUND
// is a progress bar filling toward the item's due date — green → amber → red as
// the cycle runs out (red once due/overdue) — with a big countdown on the
// right and a "Done by [avatar] [name] · Nd ago" subtitle.
//
// Tapping a row opens a how-to popup (the instructions + any photos, rendered
// like Training) with Cancel / Mark as done. "Mark as done" → a PIN-LESS staff
// picker ("Who cleaned it?") → insert into side_job_completions with a points +
// name snapshot (so config edits never rewrite history) → points, celebration.
//
// All top-level idents are sj/Sj-prefixed — Babel-standalone scripts share one
// global scope (HANDBOOK §9). Hooks are aliased with an Sj suffix.

const { useState: useSj, useEffect: useSjEffect, useMemo: useSjMemo } = React;

// How far back we fetch completions. 200 days > any reasonable cycle, so an
// item with no completion in this window is genuinely "never cleared".
const SJ_WINDOW_DAYS = 200;

// Device-local YYYY-MM-DD (matches the portal's isoDate()).
const sjComputeKey = () => {
  const d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
};

// Client UUID so the optimistic row + its realtime echo share one identity.
const sjNewCompletionId = () => {
  if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0;
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
};

// Whole-day difference between two local YYYY-MM-DD keys (a - b), parsed by
// hand (new Date('YYYY-MM-DD') is UTC and drifts a day in negative timezones).
const sjDaysBetween = (aKey, bKey) => {
  const p = (k) => { const [y, m, d] = String(k).split('-').map(Number); return new Date(y, (m || 1) - 1, d || 1).getTime(); };
  return Math.round((p(aKey) - p(bKey)) / 86400000);
};

const sjDaysAgoLabel = (lastKey, todayKey) => {
  if (!lastKey) return 'never';
  const n = Math.max(0, sjDaysBetween(todayKey, lastKey));
  if (n === 0) return 'today';
  if (n === 1) return 'yesterday';
  return n + 'd ago';
};

const sjFirstName = (name) => String(name || '').trim().split(/\s+/)[0] || '';

// Status for one item given its last-done key (or null = never cleared).
// ratio drives the row fill; big/lbl the countdown; tier the "due now" count.
const SJ_GREEN = '#16A34A', SJ_AMBER = '#D97706', SJ_RED = '#DC2626';
const sjStatusOf = (item, lastKey, todayKey) => {
  const cycle = Number(item.cycleDays) || 0;
  if (!lastKey) return { tier: 'never', ratio: 999, color: SJ_RED, big: 'New', lbl: 'never cleared' };
  const daysSince = Math.max(0, sjDaysBetween(todayKey, lastKey));
  const ratio = cycle > 0 ? daysSince / cycle : 1;
  const rem = cycle - daysSince;
  if (rem < 0) return { tier: 'overdue', ratio, color: SJ_RED, big: (-rem) + 'd', lbl: 'overdue' };
  if (rem === 0) return { tier: 'due', ratio, color: SJ_RED, big: 'Today', lbl: '' };
  if (ratio >= 0.85) return { tier: 'soon', ratio, color: SJ_AMBER, big: rem + 'd', lbl: 'left' };
  return { tier: 'ok', ratio, color: SJ_GREEN, big: rem + 'd', lbl: 'left' };
};

const sjItemFromRow = (r) => ({
  id: r.id,
  name: r.label || '',
  cycleDays: Number(r.cycle_days) || 7,
  points: Number(r.points) || 0,
  instructionsHtml: r.instructions_html || '',
  sortOrder: Number(r.sort_order) || 0,
  archived: !!r.archived,
});

const SideJobScreen = ({ currentStaff, showToast }) => {
  const [todayKey, setTodayKey] = useSj(sjComputeKey);
  useSjEffect(() => {
    const t = setInterval(() => { const k = sjComputeKey(); setTodayKey(prev => (prev === k ? prev : k)); }, 30 * 1000);
    return () => clearInterval(t);
  }, []);

  // ---- Items (side_job_tasks). Fetch on mount + realtime refetch. ----
  const [sjItems, setSjItems] = useSj([]);
  useSjEffect(() => {
    if (!window.supa) return;
    let cancelled = false;
    const refetch = async () => {
      const { data, error } = await window.supa.from('side_job_tasks').select('*')
        .eq('restaurant_id', window.RESTAURANT_ID).order('sort_order');
      if (cancelled) return;
      if (error) { console.error('side_job_tasks select failed', error); return; }
      setSjItems((data || []).map(sjItemFromRow));
    };
    refetch();
    const ch = window.supa.channel('ipad-side-job-tasks:' + window.RESTAURANT_ID)
      .on('postgres_changes', { event: '*', schema: 'public', table: 'side_job_tasks', filter: 'restaurant_id=eq.' + window.RESTAURANT_ID }, () => refetch())
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, []);

  // ---- Completions over the window; kept live. ----
  const [sjCompletions, setSjCompletions] = useSj([]);
  const sjWindowStart = useSjMemo(() => {
    const [y, m, d] = todayKey.split('-').map(Number);
    const dt = new Date(y, (m || 1) - 1, d || 1);
    dt.setDate(dt.getDate() - SJ_WINDOW_DAYS);
    return dt.getFullYear() + '-' + String(dt.getMonth() + 1).padStart(2, '0') + '-' + String(dt.getDate()).padStart(2, '0');
  }, [todayKey]);
  useSjEffect(() => {
    if (!window.supa) return;
    let cancelled = false;
    window.supa.from('side_job_completions').select('*')
      .eq('restaurant_id', window.RESTAURANT_ID).gte('the_date', sjWindowStart)
      .then(({ data, error }) => {
        if (error) { console.error('side_job_completions select failed', error); return; }
        if (cancelled || !data) return;
        setSjCompletions(data);
      });
    const ch = window.supa.channel('ipad-side-job-completions:' + window.RESTAURANT_ID)
      .on('postgres_changes', { event: '*', schema: 'public', table: 'side_job_completions', filter: 'restaurant_id=eq.' + window.RESTAURANT_ID }, (payload) => {
        if (payload.eventType === 'DELETE') {
          const gone = payload.old;
          if (gone && gone.id) setSjCompletions(prev => prev.filter(c => c.id !== gone.id));
          return;
        }
        const row = payload.new;
        if (!row || !row.the_date || row.the_date < sjWindowStart) return;
        setSjCompletions(prev => {
          if (prev.some(c => c.id === row.id)) return prev.map(c => (c.id === row.id ? row : c));
          if (payload.eventType === 'INSERT') return [row, ...prev];
          return prev;
        });
      })
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, [sjWindowStart]);

  // Latest completion per task_id → { date, staffId }.
  const sjLastByTask = useSjMemo(() => {
    const m = {};
    sjCompletions.forEach(c => {
      if (!c.task_id || !c.the_date) return;
      const cur = m[c.task_id];
      if (!cur || c.the_date >= cur.date) m[c.task_id] = { date: c.the_date, staffId: c.staff_id };
    });
    return m;
  }, [sjCompletions]);

  const sjStaffById = useSjMemo(() => {
    const m = {};
    (window.SAMPLE_STAFF || []).forEach(s => { m[s.id] = s; });
    return m;
  }, []);

  // Active items → status rows, sorted most-urgent first (ratio desc).
  const sjRows = useSjMemo(() => {
    return sjItems.filter(i => !i.archived).map(item => {
      const last = sjLastByTask[item.id] || null;
      const st = sjStatusOf(item, last ? last.date : null, todayKey);
      return { item, st, lastDate: last ? last.date : null, lastStaff: last ? sjStaffById[last.staffId] : null };
    }).sort((a, b) => b.st.ratio - a.st.ratio);
  }, [sjItems, sjLastByTask, todayKey, sjStaffById]);

  const sjDueCount = sjRows.filter(r => r.st.tier === 'never' || r.st.tier === 'overdue' || r.st.tier === 'due').length;

  // ---- Detail popup + mark-done flow. phase: 'idle' | 'staff'. ----
  const [sjDetailId, setSjDetailId] = useSj(null);
  const [sjPhase, setSjPhase] = useSj('idle');
  const sjDetailItem = sjItems.find(i => i.id === sjDetailId && !i.archived) || null;

  const sjClose = () => { setSjDetailId(null); setSjPhase('idle'); };

  // If the open item gets archived/deleted out from under the flow, bail.
  useSjEffect(() => {
    if (!sjDetailId) return;
    if (!sjItems.some(i => i.id === sjDetailId && !i.archived)) {
      sjClose();
      showToast && showToast({ message: 'That item just changed — please re-pick', icon: 'x' });
    }
  }, [sjItems, sjDetailId]);

  const sjSubmit = async (staff) => {
    const item = sjDetailItem;
    setSjPhase('idle');
    if (!staff || !item) { showToast && showToast({ message: 'That item just changed — please re-pick', icon: 'x' }); return; }
    const id = sjNewCompletionId();
    const row = {
      id,
      restaurant_id: window.RESTAURANT_ID,
      task_id: item.id,
      staff_id: staff.id,
      the_date: todayKey,
      points: Number(item.points) || 0,   // snapshot
      task_label: item.name,               // snapshot
      done_at: new Date().toISOString(),
    };
    setSjCompletions(prev => [row, ...prev]);
    setSjDetailId(null);
    if (!window.supa) return;
    const { error } = await window.supa.from('side_job_completions').insert(row);
    if (error) {
      console.error('side_job_completions insert failed', error);
      setSjCompletions(prev => prev.filter(c => c.id !== id));
      showToast && showToast({ message: 'Failed to save — try again', icon: 'x' });
    } else {
      window.celebrateCompletion && window.celebrateCompletion();
    }
  };

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--bg-page)', position: 'relative' }}>
      {/* Header */}
      <div style={{ padding: '16px 24px', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-1)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 11, color: 'var(--fg-3)', fontWeight: 500, letterSpacing: '0.06em', textTransform: 'uppercase' }}>{window.fmtTodayLabel()}</div>
            <div data-reload-tap style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: 'var(--fg-1)', marginTop: 2, lineHeight: 1.1 }}>Side Jobs</div>
          </div>
          <div style={{ fontSize: 14, color: 'var(--fg-2)', fontFamily: 'var(--font-num)' }}>
            <strong style={{ color: sjDueCount > 0 ? SJ_RED : 'var(--fg-1)' }}>{sjDueCount}</strong> due now
          </div>
        </div>
      </div>

      {/* Waterfall list */}
      <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '16px 24px 32px' }}>
        {sjRows.length === 0 ? (
          <div style={{ padding: '60px 20px', textAlign: 'center', color: 'var(--fg-3)' }}>
            <div style={{ fontSize: 15 }}>No side jobs yet — add them in the admin portal.</div>
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, maxWidth: 640, margin: '0 auto' }}>
            {sjRows.map(({ item, st, lastDate, lastStaff }) => {
              const w = Math.min(st.ratio, 1) * 100;
              return (
                <button key={item.id} onClick={() => { setSjDetailId(item.id); setSjPhase('idle'); }}
                  style={{ position: 'relative', overflow: 'hidden', width: '100%', textAlign: 'left', border: '1px solid var(--border-2)', borderRadius: 16, minHeight: 74, background: 'var(--bg-surface)', cursor: 'pointer', padding: 0 }}>
                  <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: w + '%', background: st.color + '22' }} />
                  <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 14, padding: '14px 18px', minHeight: 74, boxSizing: 'border-box' }}>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 18, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.01em' }}>{item.name}</div>
                      <div style={{ fontSize: 13, color: 'var(--fg-2)', marginTop: 5, display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
                        {lastStaff ? (
                          <>Done by <Avatar staff={lastStaff} size={20} /> <span style={{ fontWeight: 500 }}>{sjFirstName(lastStaff.name)}</span> · {sjDaysAgoLabel(lastDate, todayKey)}</>
                        ) : (
                          <span style={{ color: 'var(--fg-3)' }}>Not cleared yet</span>
                        )}
                      </div>
                    </div>
                    <div style={{ textAlign: 'right', whiteSpace: 'nowrap', color: st.color, display: 'flex', alignItems: 'baseline', gap: 5 }}>
                      <span style={{ fontSize: 26, fontWeight: 700, lineHeight: 1 }}>{st.big}</span>
                      {st.lbl && <span style={{ fontSize: 12, color: 'var(--fg-2)' }}>{st.lbl}</span>}
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        )}
      </div>

      {/* DETAIL popup — how-to instructions + Cancel / Mark as done. */}
      <Modal open={!!sjDetailItem} onClose={sjClose} width={560} title={sjDetailItem ? sjDetailItem.name : ''}>
        {sjDetailItem && (
          <div style={{ padding: '4px 22px 22px' }}>
            <div style={{ fontSize: 12, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg-3)', margin: '4px 0 10px' }}>What to do</div>
            {sjDetailItem.instructionsHtml && sjDetailItem.instructionsHtml.replace(/<[^>]*>/g, '').trim() ? (
              <div className="sj-instr" style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--fg-1)' }}
                dangerouslySetInnerHTML={{ __html: window.DOMPurify ? window.DOMPurify.sanitize(sjDetailItem.instructionsHtml, { USE_PROFILES: { html: true } }) : '' }} />
            ) : (
              <div style={{ fontSize: 14, color: 'var(--fg-3)', fontStyle: 'italic' }}>No instructions added for this job yet.</div>
            )}
            <div style={{ fontSize: 13, color: 'var(--fg-3)', marginTop: 14 }}>
              {'every ' + sjDetailItem.cycleDays + (sjDetailItem.cycleDays === 1 ? ' day' : ' days')} · {sjDetailItem.points} pts
            </div>
            <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
              <button onClick={sjClose} style={{ flex: 1, height: 52, borderRadius: 14, background: 'var(--bg-page)', color: 'var(--fg-1)', border: '1px solid var(--border-1)', fontSize: 16, fontWeight: 500, cursor: 'pointer' }}>Cancel</button>
              <button onClick={() => setSjPhase('staff')} style={{ flex: 1, height: 52, borderRadius: 14, background: '#1D4ED8', color: '#FFFFFF', border: 'none', fontSize: 16, fontWeight: 600, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, boxShadow: '0 4px 14px rgba(29,78,216,0.30)' }}>
                <Icon name="check" size={18} color="#fff" strokeWidth={2.4} /> Mark as done
              </button>
            </div>
          </div>
        )}
      </Modal>

      {/* PIN-less staff picker — pick who cleared it, then log. */}
      <StaffPicker
        open={sjPhase === 'staff'}
        onClose={() => setSjPhase('idle')}
        onPick={sjSubmit}
        title="Who cleared it?"
        subtitle={sjDetailItem ? sjDetailItem.name : ''}
      />

      <style>{`.sj-instr img{max-width:100%;height:auto;border-radius:10px;margin:6px 0;} .sj-instr p{margin:0 0 8px;} .sj-instr:last-child{margin-bottom:0;}`}</style>
    </div>
  );
};

Object.assign(window, { SideJobScreen });
