// Disciplines.jsx — written notices issued to one crew member, emailed to
// them, and acknowledged by signing with their PIN (v19.00).
//
// The memo is three questions on purpose — what happened, why it matters,
// what's expected next — because a notice that only says what happened is
// a complaint, and one without an expectation gives the person nothing to
// do differently. Each field has the Rewrite chip, so a note typed in
// frustration at 11pm can be turned into something you'd be happy to have
// read back in a dispute.
//
// SECURITY, and it differs from every other page here: this page reads
// discipline_memos with the admin's own session, and that table has NO
// anon policy at all. The employee's copy is served by the
// `discipline-memo` edge function, matched by token with the service role.
// Do not add an anon select policy to make anything here simpler — the
// anon key ships inside the client bundle, and these rows name people and
// describe their conduct. Sending and signing both go through the same
// function: send is gated on a real admin session, signing verifies the
// PIN server-side so it never reaches the browser.
//
// All top-level idents are dm/Dm/DM_-prefixed — Babel-standalone shares one
// global scope across every .jsx in the portal (HANDBOOK §9).

const dmFromRow = (r) => ({
  id: r.id,
  staffId: r.staff_id,
  incidentDate: r.incident_date || '',
  whatHappened: r.what_happened || '',
  whyItMatters: r.why_it_matters || '',
  expectations: r.expectations || '',
  status: r.status || 'draft',
  issuedByEmail: r.issued_by_email || '',
  sentTo: r.sent_to || '',
  sentAt: r.sent_at || null,
  signedAt: r.signed_at || null,
  createdAt: r.created_at || null,
});
// NOTE: never send restaurant_id — useSupaList injects it on inserts. Also
// never send sign_token / sent_at / signed_at: those are the edge
// function's to write, and a client that thinks it owns them will race it.
const dmToRow = (o) => ({
  id: o.id,
  staff_id: o.staffId,
  incident_date: o.incidentDate || null,
  what_happened: o.whatHappened || '',
  why_it_matters: o.whyItMatters || '',
  expectations: o.expectations || '',
  status: o.status || 'draft',
  updated_at: new Date().toISOString(),
});

const DM_STATUS = {
  draft:  { label: 'Draft',  bg: 'var(--bg-sunken)',  fg: 'var(--fg-2)' },
  sent:   { label: 'Sent',   bg: 'var(--warning-bg)', fg: 'var(--warning)' },
  signed: { label: 'Signed', bg: 'var(--success-bg)', fg: 'var(--success)' },
};

const dmFmtDate = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
};
const dmFmtDateTime = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
    d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
};
const dmPlainDate = (ymd) => {
  if (!ymd) return '—';
  const [y, m, d] = String(ymd).split('-').map(Number);
  if (!y) return String(ymd);
  return new Date(y, m - 1, d).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
};

// Sessions identify an admin by email, but a panel that says a memo was
// issued "by ryan@snap-a-box.com" is showing plumbing where a person
// belongs. Falls back to the address when nothing matches.
const dmPerson = (email) => {
  const e = String(email || '').trim().toLowerCase();
  if (!e) return '';
  const hit = (window.SAMPLE_STAFF || []).find(s => (s.email || '').trim().toLowerCase() === e);
  return hit ? hit.name : email;
};

// Admins issue memos; they don't receive them.
const dmRecipients = () => (window.SAMPLE_STAFF || [])
  .filter(s => s.active !== false && s.role !== 'Admin')
  .sort((a, b) => (a.name || '').localeCompare(b.name || ''));

const Disciplines = () => {
  const [memos, setMemos] = window.useSupaList('discipline_memos', {
    fromRow: dmFromRow, toRow: dmToRow, initial: [],
  });
  const [filter, setFilter] = useState('open');
  const [editing, setEditing] = useState(null);
  const [toast, setToast] = useState('');

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

  const rows = useMemo(() => memos.slice().sort((a, b) =>
    String(b.sentAt || b.createdAt || '').localeCompare(String(a.sentAt || a.createdAt || ''))), [memos]);

  const counts = {
    draft: rows.filter(r => r.status === 'draft').length,
    sent: rows.filter(r => r.status === 'sent').length,
    signed: rows.filter(r => r.status === 'signed').length,
  };
  const visible = rows.filter(r => filter === 'all' ? true : filter === 'open' ? r.status !== 'signed' : r.status === filter);

  const newMemo = () => setEditing({
    id: 'new', staffId: '', incidentDate: '', whatHappened: '', whyItMatters: '',
    expectations: '', status: 'draft',
  });

  // Returns the saved object so the caller can keep working with the real
  // id. `silent` is for the send path, where "Draft saved" would only flash
  // past on its way to being replaced by "Sent to …".
  const saveMemo = (draft, silent) => {
    if (draft.id === 'new') {
      const next = Object.assign({}, draft, { id: crypto.randomUUID(), createdAt: new Date().toISOString() });
      setMemos(prev => prev.concat([next]));
      setEditing(next);
      if (!silent) setToast('Draft saved.');
      return next;
    }
    setMemos(prev => prev.map(m => m.id === draft.id ? draft : m));
    if (!silent) setToast('Draft saved.');
    return draft;
  };

  // Deleting is allowed in every state, but the confirm has to be honest
  // about what is and isn't being undone: a sent memo is already in
  // somebody's inbox, and deleting our row doesn't reach into it. What it
  // DOES do is kill the sign link, since the function matches on a row
  // that no longer exists.
  const deleteMemo = (memo) => {
    const who = (staffById[memo.staffId] || {}).name || 'this person';
    const msg = memo.status === 'draft'
      ? 'Delete this draft? It hasn’t been sent, so nobody has seen it.'
      : memo.signedAt
        ? 'Delete this memo?\n\n' + who + ' already signed it. Deleting removes our record of that — the copy in their inbox stays, but nothing here will show it was ever issued or acknowledged.'
        : 'Delete this memo?\n\nIt was already emailed to ' + (memo.sentTo || who) + '. Their copy stays in their inbox, but the sign link will stop working and no acknowledgement can be recorded.';
    if (!window.confirm(msg)) return;
    setMemos(prev => prev.filter(m => m.id !== memo.id));
    setEditing(null);
    setToast('Memo deleted.');
  };

  // The function is the only thing that may stamp sent/token/issued_by, so
  // the row is refetched afterwards rather than guessed at locally.
  const sendMemo = async (memo, overrideTo) => {
    // useSupaList fires its insert without awaiting it, so a memo written
    // and sent in one sitting can reach the function before the row exists
    // — it would answer "memo not found" for a memo the user is looking at.
    // Upserting the on-screen wording first is idempotent against that
    // pending insert, and it also guarantees the email carries what's on
    // screen rather than whatever last made it to Postgres.
    const { error: upErr } = await window.supa.from('discipline_memos')
      .upsert(Object.assign({}, dmToRow(memo), { restaurant_id: window.RESTAURANT_ID }));
    if (upErr) { setToast(upErr.message || 'Could not save before sending.'); return false; }

    const { data, error } = await window.supa.functions.invoke('discipline-memo', {
      body: { action: 'send', memo_id: memo.id, to: overrideTo || undefined },
    });
    if (error || !data || data.error) {
      const msg = (data && (data.detail || data.error)) || (error && error.message) || 'Send failed';
      setToast(msg);
      return false;
    }
    const { data: fresh } = await window.supa.from('discipline_memos').select('*').eq('id', memo.id).maybeSingle();
    if (fresh) setMemos(prev => prev.map(m => m.id === memo.id ? dmFromRow(fresh) : m));
    const ccNote = (data.cc && data.cc.length) ? ' · copied ' + data.cc.length + ' admin' + (data.cc.length === 1 ? '' : 's') : '';
    setToast('Sent to ' + data.to + ccNote);
    setEditing(null);
    return true;
  };

  return (
    <div className="portal-page-wide">
      <div className="portal-page-header">
        <div>
          <h1 className="portal-page-title">Disciplines</h1>
          <div className="portal-page-subtitle">
            Written notices to one person — what happened, why it matters, what’s expected next.
            Emailed to them, and signed with their access PIN.
          </div>
        </div>
        <PBtn variant="primary" size="md" icon="ri-add-line" onClick={newMemo}>New memo</PBtn>
      </div>

      <div style={{ marginBottom: 14 }}>
        <PSegment
          value={filter}
          onChange={setFilter}
          options={[
            { value: 'open', label: 'Open (' + (counts.draft + counts.sent) + ')' },
            { value: 'draft', label: 'Drafts (' + counts.draft + ')' },
            { value: 'sent', label: 'Awaiting signature (' + counts.sent + ')' },
            { value: 'signed', label: 'Signed (' + counts.signed + ')' },
            { value: 'all', label: 'All' },
          ]}
        />
      </div>

      <div className="portal-card">
        <table className="portal-table">
          <thead>
            <tr>
              <th>Person</th>
              <th style={{ width: 110 }}>Status</th>
              <th style={{ width: 130 }}>Incident</th>
              <th style={{ width: 150 }}>Sent</th>
              <th style={{ width: 160 }}>Signed</th>
              <th style={{ width: 40 }}></th>
            </tr>
          </thead>
          <tbody>
            {visible.length === 0 && (
              <tr><td colSpan={6} style={{ padding: 30, textAlign: 'center', color: 'var(--fg-3)', fontSize: 12.5 }}>
                {memos.length === 0 ? 'No memos yet.' : 'Nothing in this view.'}
              </td></tr>
            )}
            {visible.map(m => {
              const s = staffById[m.staffId];
              const st = DM_STATUS[m.status] || DM_STATUS.draft;
              return (
                <tr key={m.id} onClick={() => setEditing(m)} style={{ cursor: 'pointer' }}>
                  <td>
                    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}>
                      {s ? <PAvatar staff={s} size={24} /> : null}
                      <span style={{ fontWeight: 500 }}>{s ? s.name : m.staffId || '—'}</span>
                    </div>
                  </td>
                  <td>
                    <span style={{
                      display: 'inline-flex', padding: '3px 9px', borderRadius: 999,
                      background: st.bg, color: st.fg, fontSize: 11, fontWeight: 600,
                    }}>{st.label}</span>
                  </td>
                  <td style={{ color: 'var(--fg-2)', fontSize: 12.5 }}>{dmPlainDate(m.incidentDate)}</td>
                  <td style={{ color: 'var(--fg-2)', fontSize: 12.5 }}>{dmFmtDateTime(m.sentAt)}</td>
                  <td style={{ color: m.signedAt ? 'var(--success)' : 'var(--fg-3)', fontSize: 12.5, fontWeight: m.signedAt ? 500 : 400 }}>
                    {dmFmtDateTime(m.signedAt)}
                  </td>
                  <td style={{ textAlign: 'right', color: 'var(--fg-3)' }}><PIcon name="ri-arrow-right-s-line" size={16} /></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {editing && (
        <DmEditor
          memo={editing}
          staffById={staffById}
          onSave={saveMemo}
          onSend={sendMemo}
          onDelete={deleteMemo}
          onClose={() => setEditing(null)}
        />
      )}

      <PToast message={toast} open={!!toast} onClose={() => setToast('')} />
    </div>
  );
};

// ------------------------------------------------------------------
// Editor / reader
// ------------------------------------------------------------------
const DmEditor = ({ memo, staffById, onSave, onSend, onDelete, onClose }) => {
  const [draft, setDraft] = useState(memo);
  const [sending, setSending] = useState(false);
  const [overrideOpen, setOverrideOpen] = useState(false);
  const [overrideTo, setOverrideTo] = useState('');
  // Who gets copied is decided server-side off the `admins` table, so the
  // composer asks rather than guessing — otherwise the list here and the
  // list on the email drift the moment an admin is added.
  const [cc, setCc] = useState(null);
  useEffect(() => {
    let cancelled = false;
    window.supa.functions.invoke('discipline-memo', { body: { action: 'cc' } })
      .then(({ data }) => { if (!cancelled && data && data.ok) setCc(data.cc || []); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, []);
  const set = (patch) => setDraft(d => Object.assign({}, d, patch));

  // Once it's been sent, the wording is what the person received — editing
  // it afterwards would make the record disagree with their copy.
  const locked = draft.status !== 'draft';
  const recipient = staffById[draft.staffId];
  const onFile = recipient && (recipient.email || '').trim();
  const complete = draft.staffId && draft.whatHappened.trim() && draft.expectations.trim();

  // The hints coach the writing, so they're noise once the memo is only
  // being read back.
  const overrideControl = !overrideOpen ? (
    <button onClick={() => setOverrideOpen(true)} style={{
      display: 'block', marginTop: 7, background: 'transparent', border: 'none', padding: 0,
      color: 'var(--fg-2)', fontSize: 12.5, textDecoration: 'underline', cursor: 'pointer',
    }}>Send to a different address</button>
  ) : (
    <input className="portal-input" value={overrideTo} onChange={e => setOverrideTo(e.target.value)}
      placeholder="name@example.com" style={{ marginTop: 8 }} autoFocus />
  );

  const ccLine = cc === null ? null : (
    <div style={{ marginTop: 6, color: 'var(--fg-3)' }}>
      {cc.length === 0 ? 'Nobody else is copied.' : 'Copied to ' + cc.map(dmPerson).join(', ') + '.'}
    </div>
  );

  const field = (label, key, placeholder, hint) => (
    <PField label={label} hint={locked ? undefined : hint}>
      {locked
        ? <div style={{ fontSize: 13.5, lineHeight: 1.6, color: 'var(--fg-1)', whiteSpace: 'pre-wrap', background: 'var(--bg-sunken)', borderRadius: 8, padding: '10px 12px' }}>
            {draft[key] || '—'}
          </div>
        : window.AiTextField
          ? <window.AiTextField
              value={draft[key]}
              onChange={(v) => set({ [key]: v })}
              placeholder={placeholder}
              multiline
              rows={4}
              contextLabel={label}
            />
          : <textarea className="portal-input" rows={4} value={draft[key]}
              onChange={e => set({ [key]: e.target.value })} placeholder={placeholder}
              style={{ height: 'auto', padding: 10 }} />}
    </PField>
  );

  return (
    <PModal open onClose={onClose} width={720} title={locked ? 'Memo' : 'New memo'} footer={
      <>
        {draft.id !== 'new' && (
          <PBtn variant="danger" onClick={() => onDelete(draft)}>Delete</PBtn>
        )}
        <div style={{ flex: 1 }} />
        <PBtn variant="ghost" onClick={onClose}>Close</PBtn>
        {!locked && (
          <PBtn disabled={!complete} onClick={() => { onSave(draft); onClose(); }}>Save draft</PBtn>
        )}
        {(
          <PBtn
            variant="primary"
            disabled={!complete || sending || (!onFile && !overrideTo.trim())}
            onClick={async () => {
              setSending(true);
              const saved = onSave(draft, true);
              await onSend(saved, overrideTo.trim());
              setSending(false);
            }}
          >{sending ? 'Sending…' : draft.status === 'draft' ? 'Send to them' : (overrideTo.trim() ? 'Send to that address' : 'Send again')}</PBtn>
        )}
      </>
    }>
      <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 15 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 190px', gap: 12 }}>
          <PField label="Who this is for">
            {locked
              ? <div style={{ display: 'flex', alignItems: 'center', gap: 9, height: 36 }}>
                  {recipient ? <PAvatar staff={recipient} size={24} /> : null}
                  <span style={{ fontSize: 14, fontWeight: 500 }}>{recipient ? recipient.name : draft.staffId}</span>
                </div>
              : <select className="portal-input" value={draft.staffId} onChange={e => set({ staffId: e.target.value })}>
                  <option value="">Choose someone…</option>
                  {dmRecipients().map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
                </select>}
          </PField>
          <PField label="Date of the incident">
            {locked
              ? <div style={{ fontSize: 14, height: 36, display: 'flex', alignItems: 'center' }}>{dmPlainDate(draft.incidentDate)}</div>
              : <input type="date" className="portal-input" value={draft.incidentDate || ''}
                  onChange={e => set({ incidentDate: e.target.value })} />}
          </PField>
        </div>

        {field('What happened', 'whatHappened',
          'Just the facts — what you saw, when, and who was involved.',
          'Stick to what happened rather than how it felt. Rewrite will tidy the wording without softening it.')}
        {field('Why it matters', 'whyItMatters',
          'The effect it had — on a customer, on the crew, on food safety.',
          'The part that turns a telling-off into a reason.')}
        {field('Expectations going forward', 'expectations',
          'What you need to see instead, specifically enough to be checked later.',
          'If this is ever revisited, this is the line that gets read first.')}

        {locked ? (
          <div style={{ fontSize: 12.5, color: 'var(--fg-2)', background: 'var(--bg-sunken)', borderRadius: 8, padding: '11px 13px', lineHeight: 1.6 }}>
            Sent {dmFmtDateTime(draft.sentAt)} to <strong>{draft.sentTo || '—'}</strong>
            {draft.issuedByEmail ? <> by <strong>{dmPerson(draft.issuedByEmail)}</strong></> : null}.
            {draft.signedAt
              ? <> Signed <strong style={{ color: 'var(--success)' }}>{dmFmtDateTime(draft.signedAt)}</strong>.</>
              : <> Not signed yet — sending again re-sends the same link.</>}
            {ccLine}
            <div style={{ marginTop: 6, color: 'var(--fg-3)' }}>
              The wording is locked now that it’s been sent, so this record matches the copy they received.
            </div>
            {overrideControl}
          </div>
        ) : (
          <div style={{ fontSize: 12.5, color: onFile ? 'var(--fg-2)' : 'var(--danger)', background: 'var(--bg-sunken)', borderRadius: 8, padding: '11px 13px', lineHeight: 1.6 }}>
            {onFile
              ? <>Sending emails <strong>{onFile}</strong>, from their Crew record.</>
              : <>No email on file for {recipient ? recipient.name : 'this person'} — add one on the Crew page (it can be pulled straight from Square), or send to an address just this once.</>}
            {ccLine}
            {overrideControl}
          </div>
        )}
      </div>
    </PModal>
  );
};

window.Disciplines = Disciplines;
