// Modals + Bulk + Data + Tweaks UI

function PrecinctModal({ precinct, store, updateStore, baseline, baselineKey, tweaks, onClose, flash }) {
  const p = precinct;
  const entries = store.turnoutEntries[p.id] || [];
  const result = store.results[p.id] || {};
  const base = baseline[p.id];

  const [count, setCount] = React.useState('');
  const [tsLocal, setTsLocal] = React.useState(() => isoLocalNow());
  const [resultsForm, setResultsForm] = React.useState(() => {
    const r = {};
    window.CANDIDATES.forEach(c => r[c.id] = result[c.id] != null ? String(result[c.id]) : '');
    r.totalBallots = result.totalBallots != null ? String(result.totalBallots) : '';
    return r;
  });
  const visibleCands = window.CANDIDATES.filter(c => tweaks.showCandidates.includes(c.id));
  const countRef = React.useRef(null);

  // Autofocus the count field when modal opens
  React.useEffect(() => {
    const id = setTimeout(() => countRef.current && countRef.current.focus(), 50);
    return () => clearTimeout(id);
  }, [p.id]);

  // Lock body scroll while modal is open (esp. iOS Safari)
  React.useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, []);

  // Esc to close
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const addEntry = () => {
    const n = parseInt(count, 10);
    if (isNaN(n) || n < 0) { flash('Enter a valid count'); return; }
    const d = new Date(tsLocal);
    if (isNaN(d.getTime())) { flash('Invalid timestamp'); return; }
    const ts = d.toISOString();
    updateStore(s => {
      const e = { ...(s.turnoutEntries) };
      e[p.id] = [...(e[p.id] || []), { ts, count: n }];
      return { ...s, turnoutEntries: e };
    });
    setCount('');
    flash(`Logged ${n} for ${p.id} at ${fmtTime(ts)}`);
    // Re-focus for the next entry
    setTimeout(() => countRef.current && countRef.current.focus(), 0);
  };

  const deleteEntry = (idx) => {
    const removed = entries[idx];
    updateStore(s => {
      const e = { ...(s.turnoutEntries) };
      e[p.id] = (e[p.id] || []).filter((_, i) => i !== idx);
      if (e[p.id].length === 0) delete e[p.id];
      return { ...s, turnoutEntries: e };
    });
    flash(`Deleted ${fmtNum(removed.count)} @ ${fmtTime(removed.ts)} · tap to undo`, () => {
      updateStore(s => {
        const e = { ...(s.turnoutEntries) };
        e[p.id] = [...(e[p.id] || []), removed];
        return { ...s, turnoutEntries: e };
      });
    });
  };

  const onCountKeyDown = (e) => {
    if (e.key === 'Enter') { e.preventDefault(); addEntry(); }
  };

  const saveResults = () => {
    const r = {};
    let any = false;
    window.CANDIDATES.forEach(c => {
      const v = resultsForm[c.id];
      if (v !== '' && !isNaN(parseInt(v, 10))) { r[c.id] = parseInt(v, 10); any = true; }
    });
    if (resultsForm.totalBallots !== '' && !isNaN(parseInt(resultsForm.totalBallots, 10))) {
      r.totalBallots = parseInt(resultsForm.totalBallots, 10); any = true;
    }
    updateStore(s => {
      const res = { ...s.results };
      if (any) res[p.id] = r; else delete res[p.id];
      return { ...s, results: res };
    });
    flash(`Results saved for ${p.id}`);
  };

  const enteredVotes = visibleCands.reduce((s, c) => s + (parseInt(resultsForm[c.id] || 0, 10) || 0), 0);
  const totalBallots = parseInt(resultsForm.totalBallots || 0, 10) || 0;
  const denom = totalBallots || enteredVotes;
  const loweryPct = denom ? (parseInt(resultsForm.lowery || 0, 10) || 0) / denom : 0;

  const sortedEntries = [...entries].sort((a, b) => new Date(b.ts) - new Date(a.ts));
  const latest = sortedEntries[0];
  const expectedNow = base ? Math.round(base.eday * window.shareAt(new Date())) : null;

  // Dirty guard: if user has typed an unsaved count or modified results form,
  // backdrop click should confirm before discarding. Esc + explicit Close
  // skip the confirm — those are intentional dismissals.
  const resultsDirty = window.CANDIDATES.some(c => {
    const cur = (resultsForm[c.id] || '').trim();
    const saved = result[c.id] != null ? String(result[c.id]) : '';
    return cur !== saved;
  }) || ((resultsForm.totalBallots || '').trim() !== (result.totalBallots != null ? String(result.totalBallots) : ''));
  const isDirty = count.trim().length > 0 || resultsDirty;
  const tryClose = () => {
    if (isDirty && !confirm('Discard unsaved entries?')) return;
    onClose();
  };

  return (
    <div className="modal-backdrop" onClick={tryClose}>
      <div className="modal" onClick={e=>e.stopPropagation()} style={{maxWidth: 680}}>
        <div className="modal-head">
          <div>
            <h3>{p.id} · {p.location}</h3>
            <div className="muted" style={{fontSize:12, marginTop:2}}>{p.address}, {p.city} {p.zip}</div>
          </div>
          <div style={{flex:1}}></div>
          <button className="btn ghost sm" onClick={onClose}>Close</button>
        </div>
        <div className="modal-body">
          <div className="drill">
            <div className="cell"><div className="lbl">Registered</div><div className="val">{base ? fmtNum(base.registered) : '—'}</div></div>
            <div className="cell"><div className="lbl">2022 EDay</div><div className="val">{window.TURNOUT_2022[p.id] ? fmtNum(window.TURNOUT_2022[p.id].eday) : '—'}</div></div>
            <div className="cell"><div className="lbl">2023 EDay</div><div className="val">{window.TURNOUT_2023[p.id] ? fmtNum(window.TURNOUT_2023[p.id].eday) : '—'}</div></div>
            <div className="cell"><div className="lbl">2022 EV</div><div className="val">{window.TURNOUT_2022[p.id] ? fmtNum(window.TURNOUT_2022[p.id].ev) : '—'}</div></div>
            <div className="cell"><div className="lbl">2023 EV</div><div className="val">{window.TURNOUT_2023[p.id] ? fmtNum(window.TURNOUT_2023[p.id].ev) : '—'}</div></div>
            <div className="cell"><div className="lbl">Expected now (vs {baselineKey})</div><div className="val">{fmtNum(expectedNow)}</div></div>
          </div>

          <h4 style={{marginTop: 18, fontSize: 12, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--vs-fg3)'}}>Turnout — Log a check-in</h4>
          <div className="entry-row">
            <div className="field entry-count">
              <label>Cumulative count</label>
              <input
                ref={countRef}
                className="input num"
                value={count}
                onChange={e=>setCount(e.target.value)}
                onKeyDown={onCountKeyDown}
                placeholder="e.g. 187"
                inputMode="numeric"
                pattern="[0-9]*"
                autoComplete="off"
              />
            </div>
            <div className="field entry-ts">
              <label>
                Timestamp
                <button type="button" className="link-btn" onClick={() => setTsLocal(isoLocalNow())}>now</button>
              </label>
              <input className="input" type="datetime-local" value={tsLocal} onChange={e=>setTsLocal(e.target.value)} />
            </div>
            <button className="btn teal entry-add" onClick={addEntry}>Add</button>
          </div>

          {sortedEntries.length > 0 && (
            <div style={{marginTop: 12}}>
              <table className="tbl" style={{fontSize:12}}>
                <thead>
                  <tr><th>Time</th><th className="num">Count</th><th className="num">Δ</th><th></th></tr>
                </thead>
                <tbody>
                  {sortedEntries.map((e, i) => {
                    const prev = sortedEntries[i + 1];
                    const delta = prev ? e.count - prev.count : null;
                    const origIdx = entries.indexOf(e);
                    return (
                      <tr key={e.ts + '-' + i}>
                        <td>{fmtTime(e.ts)}</td>
                        <td className="num"><b>{fmtNum(e.count)}</b></td>
                        <td className={'num ' + (delta == null ? 'muted' : delta > 0 ? 'delta-pos' : 'delta-neg')}>
                          {delta != null ? (delta >= 0 ? '+' : '') + fmtNum(delta) : '—'}
                        </td>
                        <td><button className="btn sm danger" onClick={()=>deleteEntry(origIdx)} title="Delete this check-in" aria-label="Delete this check-in">Delete</button></td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          )}

          <h4 style={{marginTop: 24, fontSize: 12, letterSpacing:'0.1em', textTransform:'uppercase', color:'var(--vs-fg3)'}}>End-of-Night Results</h4>
          <p className="muted" style={{fontSize:12, marginTop:0}}>Leave any candidate blank if you don't want to enter them. Total Ballots Cast is used as the denominator for percentages.</p>
          <div className="results-grid">
            {visibleCands.map(c => (
              <div className="field" key={c.id} style={{marginBottom:6}}>
                <label>{c.name}{c.isClient ? ' ★' : ''}</label>
                <input
                  className="input num"
                  value={resultsForm[c.id]}
                  onChange={e=>setResultsForm({...resultsForm, [c.id]: e.target.value})}
                  placeholder="—"
                  inputMode="numeric"
                  pattern="[0-9]*"
                  autoComplete="off"
                />
              </div>
            ))}
            <div className="field" style={{marginBottom:6, gridColumn:'1 / -1', borderTop:'1px solid var(--vs-border)', paddingTop:8}}>
              <label>Total Ballots Cast (precinct)</label>
              <input
                className="input num"
                value={resultsForm.totalBallots}
                onChange={e=>setResultsForm({...resultsForm, totalBallots: e.target.value})}
                placeholder="—"
                inputMode="numeric"
                pattern="[0-9]*"
                autoComplete="off"
              />
            </div>
          </div>
          {(enteredVotes > 0 || totalBallots > 0) && (
            <div className="muted" style={{fontSize:12, marginTop:8}}>
              Entered: {fmtNum(enteredVotes)} · Denominator: {fmtNum(denom)} · Lowery: <b style={{color:'var(--vs-teal)'}}>{fmtPct(loweryPct, 1)}</b>
            </div>
          )}
        </div>
        <div className="modal-foot">
          <button className="btn ghost" onClick={onClose}>Done</button>
          <button className="btn" onClick={saveResults}>Save Results</button>
        </div>
      </div>
    </div>
  );
}

function BulkEntryModal({ precincts, store, updateStore, onClose, flash }) {
  const [text, setText] = React.useState('');
  const [tsLocal, setTsLocal] = React.useState(() => isoLocalNow());
  const tryCloseRef = React.useRef(() => onClose());

  React.useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => { if (e.key === 'Escape') tryCloseRef.current(); };
    window.addEventListener('keydown', onKey);
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener('keydown', onKey);
    };
  }, []);

  // Parse the textarea live so the user can preview before applying.
  const parsed = React.useMemo(() => {
    const validIds = {};
    precincts.forEach(p => validIds[p.id] = true);
    const rows = [];
    const lines = text.split(/\r?\n/);
    for (const raw of lines) {
      const line = raw.trim();
      if (!line) continue;
      const m = line.match(/^(\d{2}-\d{2})[\s,\t]+(\d+)\b/);
      if (!m) { rows.push({ raw: line, ok: false, reason: 'unparseable' }); continue; }
      if (!validIds[m[1]]) { rows.push({ raw: line, ok: false, reason: 'unknown precinct ' + m[1] }); continue; }
      rows.push({ raw: line, ok: true, id: m[1], count: parseInt(m[2], 10) });
    }
    const ok = rows.filter(r => r.ok);
    return { rows, ok, bad: rows.length - ok.length };
  }, [text, precincts]);

  const tryClose = () => {
    if (text.trim().length > 0 && !confirm('Discard pasted entries?')) return;
    onClose();
  };
  // Keep the ref pointing to the latest closure so the Esc handler always
  // sees the current text/onClose without re-attaching the listener.
  tryCloseRef.current = tryClose;

  const apply = () => {
    const d = new Date(tsLocal);
    if (isNaN(d.getTime())) { flash('Invalid timestamp'); return; }
    if (parsed.ok.length === 0) { flash('No valid lines parsed. Format: "01-01 187"'); return; }
    const ts = d.toISOString();
    updateStore(s => {
      const e = { ...s.turnoutEntries };
      for (const u of parsed.ok) {
        e[u.id] = [...(e[u.id] || []), { ts, count: u.count }];
      }
      return { ...s, turnoutEntries: e };
    });
    flash(`Logged ${parsed.ok.length} precincts at ${fmtTime(ts)}${parsed.bad ? ` (${parsed.bad} skipped)` : ''}`);
    onClose();
  };

  return (
    <div className="modal-backdrop" onClick={tryClose}>
      <div className="modal" onClick={e=>e.stopPropagation()} style={{maxWidth: 600}}>
        <div className="modal-head"><h3>Bulk Turnout Entry</h3></div>
        <div className="modal-body">
          <p className="muted" style={{fontSize:12, marginTop:0}}>
            Paste one precinct per line: <code>01-01 187</code> · <code>01-01,187</code> · <code>01-01\t187</code>. All entries get the same timestamp.
          </p>
          <div className="field">
            <label>
              Timestamp for batch
              <button type="button" className="link-btn" onClick={() => setTsLocal(isoLocalNow())}>now</button>
            </label>
            <input className="input" type="datetime-local" value={tsLocal} onChange={e=>setTsLocal(e.target.value)} />
          </div>
          <div className="field">
            <label>Precinct counts</label>
            <textarea
              className="textarea"
              rows={10}
              value={text}
              onChange={e=>setText(e.target.value)}
              placeholder={'01-01 187\n01-02 142\n01-03 220'}
              autoCapitalize="off"
              autoCorrect="off"
              spellCheck="false"
            />
          </div>
          {parsed.rows.length > 0 && (
            <>
              <div className="bulk-preview" aria-label="Parse preview">
                {parsed.rows.map((r, i) => (
                  <div key={i} className={'bulk-preview-row ' + (r.ok ? 'ok' : 'bad')}>
                    {r.ok ? '✓' : '✗'}
                    <span style={{flex:1}}>{r.raw}</span>
                    {r.ok ? <span>→ {fmtNum(r.count)}</span> : <span style={{fontStyle:'italic'}}>{r.reason}</span>}
                  </div>
                ))}
              </div>
              <div className="bulk-preview-summary">
                {parsed.ok.length} valid · {parsed.bad} skipped
              </div>
            </>
          )}
        </div>
        <div className="modal-foot">
          <button className="btn ghost" onClick={tryClose}>Cancel</button>
          <button
            className="btn teal"
            onClick={apply}
            disabled={parsed.ok.length === 0}
            style={parsed.ok.length === 0 ? {opacity: 0.5, cursor: 'not-allowed'} : {}}
          >Apply Batch ({parsed.ok.length})</button>
        </div>
      </div>
    </div>
  );
}

function DataPage({ store, setStore, flash }) {
  const exportData = () => {
    const blob = new Blob([JSON.stringify(store, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = `lowery-tracker-${new Date().toISOString().slice(0,16).replace(':','')}.json`;
    a.click();
    URL.revokeObjectURL(url);
    flash('Exported');
  };
  const importData = (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = ev => {
      try {
        const data = JSON.parse(ev.target.result);
        setStore(data);
        flash('Imported');
      } catch (err) { flash('Invalid JSON'); }
    };
    reader.readAsText(file);
  };
  const reset = () => {
    if (confirm('Erase all turnout entries and results? This cannot be undone.')) {
      setStore({ turnoutEntries: {}, results: {}, evCountywide: 77136, notes: '' });
      flash('Cleared');
    }
  };

  const turnoutCount = Object.values(store.turnoutEntries || {}).reduce((s, e) => s + e.length, 0);
  const resultsCount = Object.keys(store.results || {}).length;

  return (
    <div>
      <div className="section">
        <div className="section-head"><h2 className="section-title">Data Management</h2></div>
        <div className="section-body">
          <div className="kpi-row" style={{marginBottom: 16}}>
            <div className="kpi"><div className="kpi-label">Turnout entries</div><div className="kpi-value">{turnoutCount}</div></div>
            <div className="kpi"><div className="kpi-label">Precincts with results</div><div className="kpi-value">{resultsCount}</div></div>
            <div className="kpi"><div className="kpi-label">EV banked (countywide)</div><div className="kpi-value">{fmtNum(store.evCountywide)}</div></div>
          </div>

          <div className="field">
            <label>Early Vote countywide total (editable)</label>
            <input className="input num" style={{maxWidth: 240}}
              value={store.evCountywide}
              onChange={e=>setStore({...store, evCountywide: parseInt(e.target.value, 10) || 0})}
              inputMode="numeric"
              pattern="[0-9]*"
            />
            <span className="muted" style={{fontSize:11, marginTop:4}}>Pre-loaded with EV through April 30, 2026 (77,136 ballots cast). Update with final EV when available.</span>
          </div>

          <div className="row" style={{marginTop: 16}}>
            <button className="btn" onClick={exportData}>Export JSON</button>
            <label className="btn ghost" style={{cursor:'pointer'}}>
              Import JSON
              <input type="file" accept="application/json" onChange={importData} style={{display:'none'}} />
            </label>
            <div className="spacer"></div>
            <button className="btn danger" onClick={reset}>Reset All Data</button>
          </div>

          <p className="muted" style={{fontSize:12, marginTop:16}}>
            Data lives in this browser's localStorage. Export to back up or sync between devices.
          </p>
        </div>
      </div>
    </div>
  );
}

function TweaksUI({ tweaks, setTweak }) {
  if (typeof TweaksPanel === 'undefined') return null;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Display" />
        <TweakToggle label="Dark Mode (war-room)" value={tweaks.darkMode} onChange={v => setTweak('darkMode', v)} />
        <TweakRadio label="Heatmap Baseline" value={tweaks.baseline}
          options={[{value:'2022', label:'2022 Primary'},{value:'2023', label:'2023 City'}]}
          onChange={v=>setTweak('baseline', v)} />
      <TweakSection label="Targets" />
        <TweakSlider label="Lowery Win Threshold (%)" value={tweaks.winThreshold} min={20} max={50} step={1}
          onChange={v=>setTweak('winThreshold', v)} />
        <TweakSlider label="Cold Flag (% below pace)" value={tweaks.lowFlagPct} min={-40} max={-5} step={1}
          onChange={v=>setTweak('lowFlagPct', v)} />
        <TweakSlider label="Hot Flag (% above pace)" value={tweaks.highFlagPct} min={5} max={40} step={1}
          onChange={v=>setTweak('highFlagPct', v)} />
      <TweakSection label="Candidates Tracked" />
        {window.CANDIDATES.map(c => (
          <TweakToggle
            key={c.id}
            label={c.name + (c.isClient ? ' ★' : '')}
            value={tweaks.showCandidates.includes(c.id)}
            onChange={v => {
              const cur = new Set(tweaks.showCandidates);
              if (v) cur.add(c.id); else cur.delete(c.id);
              setTweak('showCandidates', [...cur]);
            }}
          />
        ))}
    </TweaksPanel>
  );
}

function isoLocalNow() {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

Object.assign(window, { PrecinctModal, BulkEntryModal, DataPage, TweaksUI });
