// src/features/admin/ui/admin-leads-campaigns.jsx
//
// "By search" section of the Teacher Leads tab: Teacher Finder outreach grouped
// by the search that produced it. Each search is a collapsible group (summary +
// every teacher found, messaged or skipped, with state/rate/distance/chat).
// Status filters come from window.LEADS_FILTER + <LeadsFilterBar> (admin-leads-filters.jsx).
// Data: window.ADMIN_DATA.teacherLeads.campaigns(). Styling: TF_TONE. Load AFTER
// admin-teacher-finder.jsx + admin-leads-filters.jsx, BEFORE admin-leads.jsx.

// Local copies of the tiny formatters admin-leads.jsx also uses (kept standalone).
const CMP_STATE_BADGE = {
  outreach_sent:         { label: 'Outreach sent',  bg: 'oklch(92% 0.03 265)', text: 'oklch(35% 0.04 265)' },
  greeted:               { label: 'Greeted',        bg: 'oklch(92% 0.03 265)', text: 'oklch(35% 0.04 265)' },
  awaiting_rate:         { label: 'Awaiting rate',   bg: 'oklch(90% 0.10 90)',  text: 'oklch(35% 0.10 80)' },
  awaiting_availability: { label: 'Awaiting avail.', bg: 'oklch(90% 0.10 90)',  text: 'oklch(35% 0.10 80)' },
  awaiting_location:     { label: 'Awaiting location', bg: 'oklch(90% 0.10 90)', text: 'oklch(35% 0.10 80)' },
  ready_for_review:      { label: 'Ready for review',bg: 'oklch(90% 0.12 145)', text: 'oklch(33% 0.12 145)' },
  opted_out:             { label: 'Opted out',       bg: 'oklch(90% 0.07 25)',  text: 'oklch(40% 0.15 25)' },
  dead:                  { label: 'Dead',            bg: 'oklch(88% 0.01 265)', text: 'oklch(45% 0.02 265)' },
  human_requested:       { label: 'Human requested', bg: 'oklch(88% 0.10 300)', text: 'oklch(38% 0.14 300)' },
  paused:                { label: 'Paused',          bg: 'oklch(90% 0.06 60)',  text: 'oklch(40% 0.10 60)' },
};

const CmpStateBadge = ({ state }) => {
  const cfg = CMP_STATE_BADGE[state] || { label: state || '—', bg: TF_TONE.border, text: TF_TONE.muted };
  return (
    <span style={{
      display: 'inline-block', padding: '3px 9px', borderRadius: 999,
      fontSize: 10, fontWeight: 700, letterSpacing: '0.03em',
      background: cfg.bg, color: cfg.text, whiteSpace: 'nowrap',
    }}>{cfg.label}</span>
  );
};

function cmpFmtRate(cents, raw) {
  if (cents != null) return `$${(cents / 100).toFixed(cents % 100 === 0 ? 0 : 2)}/hr`;
  if (raw) return raw;
  return '—';
}

// How an SM delivery status reads — shared by the opener badge (State column) and
// the per-message caption in the mirror. null = show nothing; "undelivered" = landline.
function cmpDeliveryView(status) {
  const s = (status || '').toLowerCase();
  if (!s) return null;
  if (['undelivered', 'failed', 'rejected', 'invalid', 'undeliverable'].includes(s))
    return { label: 'Not delivered', color: 'oklch(42% 0.17 25)', bg: 'oklch(90% 0.09 25)', note: "this number can't receive texts — likely a landline" };
  if (s === 'delivered')
    return { label: 'Delivered', color: 'oklch(34% 0.12 145)', bg: 'oklch(92% 0.09 145)', note: null };
  if (['sent', 'queued', 'sending', 'accepted', 'scheduled', 'pending'].includes(s))
    return { label: 'Sending…', color: TF_TONE.muted, bg: TF_TONE.border, note: null };
  return null;
}

// Opener delivery badge for the State column (collapsed view).
const CmpDeliveryNote = ({ status }) => {
  const cfg = cmpDeliveryView(status);
  if (!cfg) return null;
  return (
    <div style={{ marginTop: 4 }}>
      <span style={{
        display: 'inline-block', padding: '2px 7px', borderRadius: 999, fontSize: 9, fontWeight: 800,
        letterSpacing: '0.03em', textTransform: 'uppercase', background: cfg.bg, color: cfg.color,
      }}>{cfg.label}</span>
      {cfg.note && (
        <div style={{ fontSize: 10, color: TF_TONE.muted, marginTop: 2, maxWidth: 160, whiteSpace: 'normal', lineHeight: 1.3 }}>{cfg.note}</div>
      )}
    </div>
  );
};

function cmpDollars(cents) {
  if (cents == null) return null;
  return `$${(cents / 100).toFixed(cents % 100 === 0 ? 0 : 2)}`;
}

// Drive time from the searched area to the teacher, e.g. "12 min".
// Known for every found teacher (computed at search time), messaged or not.
function cmpFmtDist(driveMin) {
  if (driveMin == null || isNaN(driveMin)) return null;
  if (driveMin < 1) return '<1 min';
  return `${Math.round(driveMin)} min`;
}

function cmpFmtDay(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d)) return '';
  return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
}

function cmpFmtWhen(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return '—';
  return d.toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
}

// "Guitar · Times Square · May 29"
function cmpTitle(c) {
  const bits = [];
  if (c.topic) bits.push(c.topic);
  if (c.area) bits.push(c.area);
  const day = cmpFmtDay(c.createdAt);
  if (day) bits.push(day);
  return bits.join(' · ') || 'Search';
}

// "found 10, messaged 4, replied 3, rates $40–$70 (lowest $40), closest 6 min"
function cmpSummary(c) {
  let s = `found ${c.found}, messaged ${c.messaged}, replied ${c.replied}`;
  const lo = cmpDollars(c.rateMinCents);
  const hi = cmpDollars(c.rateMaxCents);
  if (lo && hi) {
    s += lo === hi ? `, rates ${lo}` : `, rates ${lo}–${hi} (lowest ${lo})`;
  }
  const mins = (c.teachers || [])
    .map(t => (t.driveMin == null || isNaN(t.driveMin)) ? null : Number(t.driveMin))
    .filter(m => m != null);
  if (mins.length) s += `, closest ${Math.round(Math.min(...mins))} min`;
  return s;
}

// Bubble side + tint + label. Inbound = teacher (left). Outbound splits by author
// so Joe sees his OWN manual texts ('agent' -> "You", green) apart from the bot.
function cmpBubbleMeta(m) {
  if (m.direction === 'inbound') return { align: 'flex-start', bg: '#fff', border: TF_TONE.border, label: 'Teacher' };
  if (m.direction === 'system')  return { align: 'center', bg: 'oklch(96% 0.01 265)', border: TF_TONE.border, label: 'System' };
  if (m.author === 'agent')      return { align: 'flex-end', bg: 'oklch(94% 0.06 145)', border: 'oklch(82% 0.08 145)', label: 'You' };
  return { align: 'flex-end', bg: 'oklch(95% 0.04 250)', border: 'oklch(85% 0.05 250)', label: 'Bot' };
}

// The conversation as a true mirror of the SM thread: teacher replies, the bot's
// auto-replies, AND Joe's manual texts — in send order, each with delivery state.
const CmpTranscript = ({ msgs }) => {
  if (!msgs || !msgs.length) {
    return <div style={{ fontSize: 12, color: TF_TONE.muted, padding: '6px 0' }}>No messages yet.</div>;
  }
  // True send order: prefer the real SM time, fall back to created_at.
  const ordered = [...msgs].sort((a, b) =>
    String(a.sm_sent_at || a.created_at || '').localeCompare(String(b.sm_sent_at || b.created_at || '')));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 7, padding: '8px 0' }}>
      {ordered.map((m, i) => {
        const r = cmpBubbleMeta(m);
        const dv = m.direction === 'outbound' ? cmpDeliveryView(m.sm_status) : null;
        return (
          <div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: r.align }}>
            <div style={{ maxWidth: '82%', background: r.bg, border: `1px solid ${r.border}`, borderRadius: 10, padding: '6px 10px' }}>
              <div style={{ fontSize: 9, fontWeight: 700, color: TF_TONE.muted, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>
                {r.label} · {cmpFmtWhen(m.sm_sent_at || m.created_at)}
              </div>
              <div style={{ fontSize: 13, color: TF_TONE.text, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{m.body || ''}</div>
            </div>
            {dv && (
              <div style={{ fontSize: 9, fontWeight: 700, color: dv.color, marginTop: 2, textTransform: 'uppercase', letterSpacing: '0.03em' }}>{dv.label}</div>
            )}
          </div>
        );
      })}
    </div>
  );
};

// One teacher row: messaged rows show state/rate/availability + Open-conversation; skipped rows greyed with a "Message" button. Lowest rate = "Best price".
const CmpTeacherRow = ({ t, isWinner, topic, searchId, outboundEnabled }) => {
  const [openChat, setOpenChat] = React.useState(false);
  // idle | sending | sent | error — local; survives until the parent reloads
  // (queue may not have drained, so we leave "Queued" rather than auto-refreshing).
  const [sendState, setSendState] = React.useState('idle');
  const [sendErr, setSendErr] = React.useState('');
  const td = { padding: '9px 10px', fontSize: 13, color: TF_TONE.text, verticalAlign: 'top' };
  const skipped = !t.outbounded;
  const hasChat = (t.transcript || []).length > 0;

  const canSend = !!t.phone && outboundEnabled
    && sendState !== 'sending' && sendState !== 'sent' && sendState !== 'duplicate';
  const sendMessage = async () => {
    if (!canSend) return;
    setSendState('sending'); setSendErr('');
    try {
      await window.ADMIN_DATA.teacherLeads.sendOutreach({
        name: t.name, phone: t.phone, topic, searchId, resultId: t.resultId,
      });
      setSendState('sent');
    } catch (e) {
      // Already in the pipeline (messaged in another search) — calm, not an error.
      if (e.code === 'DUPLICATE_PHONE' || e.code === 'DUPLICATE_QUEUED') {
        setSendState('duplicate');
        setSendErr(e.message || 'Already contacted');
      } else {
        setSendState('error');
        setSendErr(e.message || 'Send failed');
      }
    }
  };
  const sendLabel = sendState === 'sending' ? 'Sending…'
    : sendState === 'sent' ? 'Queued ✓'
    : sendState === 'duplicate' ? 'Already contacted'
    : sendState === 'error' ? 'Retry' : 'Message';
  const sendTitle = !t.phone ? 'No phone number on this teacher'
    : !outboundEnabled ? "Turn on 'Outreach enabled' in the Teacher Finder tab first"
    : sendState === 'duplicate' ? 'This number is already in your leads — not messaging it again.' : '';

  return (
    <React.Fragment>
      <tr style={{ borderBottom: `1px solid ${TF_TONE.border}`, opacity: skipped ? 0.6 : 1, background: isWinner ? 'oklch(97.5% 0.03 145)' : 'transparent' }}>
        <td style={{ ...td, fontWeight: 600 }}>
          <span>{t.name || <span style={{ color: TF_TONE.muted, fontStyle: 'italic' }}>—</span>}</span>
          {isWinner && (
            <span style={{
              display: 'inline-block', marginLeft: 7, padding: '2px 7px', borderRadius: 999,
              fontSize: 9, fontWeight: 800, letterSpacing: '0.04em', textTransform: 'uppercase',
              background: 'oklch(90% 0.12 145)', color: 'oklch(33% 0.12 145)', verticalAlign: 'middle',
            }}>★ Best price</span>
          )}
          <div style={{ fontSize: 11, fontWeight: 400, color: TF_TONE.muted, marginTop: 2 }}>{t.address || ''}</div>
        </td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}>{t.phone || <span style={{ color: TF_TONE.muted }}>—</span>}</td>
        <td style={td}>
          {skipped
            ? <span style={{ fontSize: 11, fontWeight: 700, color: TF_TONE.muted, textTransform: 'uppercase', letterSpacing: '0.04em', whiteSpace: 'nowrap' }}>Not messaged</span>
            : <CmpStateBadge state={t.leadState} />}
          {!skipped && !t.replied && <CmpDeliveryNote status={t.deliveryStatus} />}
        </td>
        <td style={{ ...td, whiteSpace: 'nowrap', fontWeight: isWinner ? 800 : 600, color: isWinner ? 'oklch(40% 0.14 145)' : TF_TONE.text }}>{skipped ? '—' : cmpFmtRate(t.rateCents, t.rateRaw)}</td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}>{cmpFmtDist(t.driveMin) || <span style={{ color: TF_TONE.muted }}>—</span>}</td>
        <td style={td}>{skipped ? <span style={{ color: TF_TONE.muted }}>—</span> : (t.availability || <span style={{ color: TF_TONE.muted }}>—</span>)}</td>
        <td style={td}>{skipped ? <span style={{ color: TF_TONE.muted }}>—</span> : (t.location || <span style={{ color: TF_TONE.muted }}>—</span>)}</td>
        <td style={{ ...td, textAlign: 'right' }}>
          {skipped ? (
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
              <button
                onClick={sendMessage}
                disabled={!canSend}
                title={sendTitle}
                style={{
                  background: sendState === 'sent' ? 'oklch(92% 0.10 145)'
                    : sendState === 'duplicate' ? TF_TONE.border : 'oklch(55% 0.13 250)',
                  border: 'none',
                  color: sendState === 'sent' ? 'oklch(33% 0.12 145)'
                    : sendState === 'duplicate' ? TF_TONE.muted : '#fff',
                  borderRadius: 7, padding: '5px 12px', fontSize: 11, fontWeight: 700,
                  cursor: canSend ? 'pointer' : 'not-allowed',
                  opacity: (!t.phone || !outboundEnabled) ? 0.5 : 1,
                  fontFamily: "'Plus Jakarta Sans', sans-serif", whiteSpace: 'nowrap',
                }}
              >{sendLabel}</button>
              {sendErr ? <span style={{ fontSize: 10, color: sendState === 'duplicate' ? TF_TONE.muted : TF_TONE.danger, maxWidth: 170, textAlign: 'right' }}>{sendErr}</span> : null}
            </div>
          ) : hasChat ? (
            <button
              onClick={() => setOpenChat(o => !o)}
              style={{
                background: 'transparent', border: `1px solid ${TF_TONE.border}`, color: TF_TONE.muted,
                borderRadius: 7, padding: '4px 9px', fontSize: 11, fontWeight: 700, cursor: 'pointer',
                fontFamily: "'Plus Jakarta Sans', sans-serif", whiteSpace: 'nowrap',
              }}
            >{openChat ? 'Hide conversation' : 'Open conversation'}</button>
          ) : null}
        </td>
      </tr>
      {openChat && (
        <tr style={{ background: TF_TONE.page }}>
          <td colSpan={8} style={{ padding: '2px 14px 10px' }}>
            <CmpTranscript msgs={t.transcript} />
          </td>
        </tr>
      )}
    </React.Fragment>
  );
};

// One collapsible search group.
const CmpGroup = ({ c, outboundEnabled }) => {
  const [open, setOpen] = React.useState(false);
  const th = { textAlign: 'left', padding: '8px 10px', fontSize: 10, fontWeight: 700, color: TF_TONE.muted, textTransform: 'uppercase', letterSpacing: '0.05em', whiteSpace: 'nowrap' };

  // The "winner" is the teacher with the lowest known hourly rate in this
  // search — only meaningful once at least two teachers have a rate, otherwise
  // there's nothing to compare. Ties (same lowest rate) all get the badge.
  const pricedRates = (c.teachers || []).map(t => t.rateCents).filter(v => v != null);
  const minRate = pricedRates.length >= 2 ? Math.min(...pricedRates) : null;

  return (
    <div style={{ background: TF_TONE.card, border: `1px solid ${TF_TONE.border}`, borderRadius: 12, marginBottom: 12, overflow: 'hidden' }}>
      <div
        onClick={() => setOpen(o => !o)}
        style={{ padding: '13px 16px', cursor: 'pointer', display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}
      >
        <span style={{ color: TF_TONE.muted }}>{open ? '▾' : '▸'}</span>
        <span style={{ fontSize: 15, fontWeight: 700, color: TF_TONE.text, fontFamily: "'Cormorant Garamond', serif" }}>
          {cmpTitle(c)}
        </span>
        <span style={{ fontSize: 12, color: TF_TONE.muted }}>— {cmpSummary(c)}</span>
      </div>
      {open && (
        <div style={{ borderTop: `1px solid ${TF_TONE.border}`, overflowX: 'auto' }}>
          {c.teachers.length === 0 ? (
            <div style={{ padding: '16px', fontSize: 13, color: TF_TONE.muted }}>No teachers recorded for this search.</div>
          ) : (
            <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 760 }}>
              <thead>
                <tr style={{ background: TF_TONE.page }}>
                  <th style={th}>Teacher</th>
                  <th style={th}>Phone</th>
                  <th style={th}>State</th>
                  <th style={th}>Rate</th>
                  <th style={th}>Distance</th>
                  <th style={th}>Availability</th>
                  <th style={th}>Location</th>
                  <th style={{ ...th, textAlign: 'right' }}>Action</th>
                </tr>
              </thead>
              <tbody>
                {c.teachers.map(t => (
                  <CmpTeacherRow
                    key={t.resultId}
                    t={t}
                    isWinner={minRate != null && t.rateCents != null && t.rateCents === minRate}
                    topic={c.topic}
                    searchId={c.id}
                    outboundEnabled={outboundEnabled}
                  />
                ))}
              </tbody>
            </table>
          )}
        </div>
      )}
    </div>
  );
};

const AdminLeadsCampaigns = () => {
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');
  const [campaigns, setCampaigns] = React.useState([]);
  const [outboundEnabled, setOutboundEnabled] = React.useState(false);
  const [filters, setFilters] = React.useState(() => window.LEADS_FILTER.defaults());

  const load = React.useCallback(async () => {
    setLoading(true); setErr('');
    try {
      const [rows, ob] = await Promise.all([
        window.ADMIN_DATA.teacherLeads.campaigns(),
        window.ADMIN_DATA.teacherLeads.getOutboundEnabled().catch(() => false),
      ]);
      setCampaigns(rows);
      setOutboundEnabled(!!ob);
    } catch (e) {
      setErr(e.message || 'Failed to load campaigns');
    } finally {
      setLoading(false);
    }
  }, []);

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

  const filtered = window.LEADS_FILTER.isActive(filters);
  const shown = window.LEADS_FILTER.apply(campaigns, filters);

  return (
    <div style={{ fontFamily: "'Plus Jakarta Sans', sans-serif", color: TF_TONE.text, marginBottom: 26 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10, gap: 12, flexWrap: 'wrap' }}>
        <div>
          <h3 style={{ margin: 0, fontFamily: "'Cormorant Garamond', serif", fontSize: 22, fontWeight: 700, color: TF_TONE.text }}>
            By search
          </h3>
          <div style={{ fontSize: 12, color: TF_TONE.muted, marginTop: 2 }}>
            Every Teacher Finder search you ran: who you found, who you messaged vs skipped, who replied, their rate, and how far each is. The lowest rate per search is flagged <strong>★ Best price</strong>. <strong>Open a conversation</strong> to see the full thread exactly as it is in Sales Message — the bot's replies, the texts you send by hand, and the teacher's replies — each with its delivery status. Or message a teacher you skipped, right here.
          </div>
        </div>
        <button
          onClick={load}
          disabled={loading}
          style={{
            background: 'transparent', border: `1px solid ${TF_TONE.border}`, borderRadius: 8,
            padding: '6px 12px', fontSize: 12, fontWeight: 600, color: TF_TONE.muted,
            cursor: loading ? 'wait' : 'pointer', fontFamily: "'Plus Jakarta Sans', sans-serif",
          }}
        >{loading ? 'Refreshing…' : 'Refresh'}</button>
      </div>

      {err && (
        <div style={{ background: TF_TONE.dangerBg, color: TF_TONE.danger, padding: '11px 13px', borderRadius: 10, fontSize: 13, marginBottom: 12 }}>
          {err}
        </div>
      )}

      {!err && !loading && campaigns.length === 0 && (
        <div style={{ background: TF_TONE.card, border: `1px solid ${TF_TONE.border}`, borderRadius: 12, padding: '20px', textAlign: 'center', color: TF_TONE.muted, fontSize: 13 }}>
          No searches yet. Run a search in the Teacher Finder tab — it'll show up here grouped by what you searched for.
        </div>
      )}

      {!err && !loading && !outboundEnabled && campaigns.length > 0 && (
        <div style={{ background: 'oklch(96% 0.05 90)', border: '1px solid oklch(85% 0.10 90)', color: 'oklch(40% 0.10 80)', padding: '9px 13px', borderRadius: 10, fontSize: 12, marginBottom: 12 }}>
          Outreach is currently <strong>off</strong> — the “Message” buttons are disabled. Flip “Outreach enabled” on in the Teacher Finder tab to message teachers from here.
        </div>
      )}

      {!err && !loading && campaigns.length > 0 && <window.LeadsFilterBar filters={filters} onChange={setFilters} campaigns={campaigns} />}

      {filtered && !shown.length && campaigns.length > 0 && (
        <div style={{ background: TF_TONE.card, border: `1px solid ${TF_TONE.border}`, borderRadius: 12, padding: '16px', textAlign: 'center', color: TF_TONE.muted, fontSize: 13 }}>No teachers match this filter.</div>
      )}

      {shown.map(c => <CmpGroup key={c.id} c={c} outboundEnabled={outboundEnabled} />)}
    </div>
  );
};

window.AdminLeadsCampaigns = AdminLeadsCampaigns;
