/* Bulk Actions — bar, action modals, deals-blocking modal, export modal, Jobs panel.
   SoT: monolith-docs/prd/prd-leads-bulk-actions.md (BR-BA-002/004/006/008/013/015/017/018/026/027 + UX). */
const { useState: useStateBA, useEffect: useEffectBA, useMemo: useMemoBA } = React;

// "4 284" — space-grouped thousands per the PRD counter format (BR-BA-004).
function fmtCount(n) { return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); }

// ---------- Bulk Actions Bar (BR-BA-002/004/027) ----------------------------
function BulkBar({ count, mode, excludedCount, matchingCount, locked, canAssign, canArchive, canExport, onSelectAllMatching, onClear, onAction }) {
  const allMatching = mode === 'all_matching';
  return (
    <div className="mn-bulkbar" role="toolbar" aria-label="Bulk actions">
      <span className="mn-bulkcount" aria-live="polite">
        <b>{fmtCount(count)}</b> selected{allMatching && excludedCount ? <span className="mn-bulkcount-ex"> ({fmtCount(excludedCount)} excluded)</span> : null}
      </span>
      {!allMatching && count < matchingCount ? (
        <button type="button" className="mn-login-link mn-bulkbar-alllink" onClick={onSelectAllMatching}>
          Select all {fmtCount(matchingCount)} matching filter
        </button>
      ) : null}
      {canAssign ? <Button variant="ghost" size="md" icon="user-plus" disabled={locked} onClick={() => onAction('assign')}>Assign</Button> : null}
      <Button variant="ghost" size="md" icon="repeat" disabled={locked} onClick={() => onAction('status')}>Status</Button>
      <Button variant="ghost" size="md" icon="tag" disabled={locked} onClick={() => onAction('tags')}>Tags</Button>
      {canArchive ? <Button variant="ghost" size="md" icon="archive" disabled={locked} onClick={() => onAction('archive')}>Archive</Button> : null}
      {canExport ? <Button variant="ghost" size="md" icon="download" disabled={locked} onClick={() => onAction('export')}>Export</Button> : null}
      <Button variant="quiet" size="md" onClick={onClear}>Clear selection</Button>
    </div>
  );
}

// Non-blocking explanatory state while the emergency lock is active (BR-BA-027).
function BulkLockBanner() {
  return (
    <div className="mn-lockbanner" role="status">
      <Icon name="shield-alert" size="sm" />
      <span>Bulk operations are temporarily locked by your administrator (security incident). Your selection is preserved — actions re-enable when the lock lifts.</span>
    </div>
  );
}

// ---------- Assign Owner modal (BR-BA-011) ----------------------------------
function BulkAssignModal({ scopeLabel, role, onClose, onConfirm }) {
  const [owner, setOwner] = useStateBA('');
  const [touched, setTouched] = useStateBA(false);
  const footer = <React.Fragment>
    <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
    <Button variant="primary" size="md" icon="user-check" disabled={touched && !owner} onClick={() => { setTouched(true); if (owner) onConfirm(owner); }}>Assign owner</Button>
  </React.Fragment>;
  return (
    <Modal open onClose={onClose} size="sm" icon="user-plus" iconTone="primary"
           eyebrow={'Bulk action · ' + scopeLabel} title="Assign owner"
           subtitle="Reassign to an active owner in your scope. Unassign to “no owner” is not available in bulk." footer={footer}>
      <Field label="Target owner" required error={touched && !owner ? 'Select an owner' : undefined}>
        <OwnerPicker value={owner} onChange={setOwner} role={role} />
      </Field>
      <div className="mn-bulk-footnote">Archived leads in the selection are skipped by policy. Logged per lead as <span className="mn-mono">lead.owner_assigned</span> with a shared <span className="mn-mono">batch_id</span>.</div>
    </Modal>
  );
}

// ---------- Change Status modal + shared Disqualify reason (BR-BA-012/013) --
function BulkStatusModal({ scopeLabel, onClose, onConfirm }) {
  const [status, setStatus] = useStateBA('');
  const [reason, setReason] = useStateBA('');
  const [text, setText] = useStateBA('');
  const [touched, setTouched] = useStateBA(false);
  const disq = status === 'disqualified';
  const textRequired = disq && reason === 'other';
  const valid = status && (!disq || (reason && (!textRequired || text.trim().length > 0)));
  const opts = ['new', 'in_progress', 'qualified', 'disqualified'].map(s => ({ value: s, label: STATUS_CFG[s].label }));
  const footer = <React.Fragment>
    <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
    <Button variant={disq ? 'destructive' : 'primary'} size="md" icon={disq ? 'x-circle' : 'repeat'} disabled={touched && !valid}
            onClick={() => { setTouched(true); if (valid) onConfirm({ status, reason, text: text.trim() }); }}>
      {disq ? 'Disqualify leads' : 'Change status'}
    </Button>
  </React.Fragment>;
  return (
    <Modal open onClose={onClose} size="sm" icon="repeat" iconTone={disq ? 'error' : 'primary'}
           eyebrow={'Bulk action · ' + scopeLabel} title="Change status"
           subtitle="The transition matrix is enforced per lead — invalid transitions go to the failure report." footer={footer}>
      <Field label="Target status" required error={touched && !status ? 'Select a status' : undefined}>
        <Select fullWidth value={status} onChange={setStatus} options={opts} placeholder="Select status…" />
      </Field>
      {disq ? (
        <React.Fragment>
          <div style={{ marginTop: 12 }}>
            <Field label="Disqualify reason (shared for the batch)" required error={touched && !reason ? 'Reason is required' : undefined}>
              <Select fullWidth value={reason} onChange={setReason} options={DISQUALIFY_REASONS} placeholder="Select a reason…" />
            </Field>
          </div>
          {textRequired ? (
            <div style={{ marginTop: 12 }}>
              <Field label="Details" required error={touched && !text.trim() ? 'Required for “Other”' : undefined}>
                <Textarea fullWidth value={text} onChange={setText} maxLength={500} placeholder="Describe the reason…" />
              </Field>
            </div>
          ) : null}
          <div className="mn-bulk-footnote">The reason applies only to leads that successfully transition to Disqualified (BR-BA-013).</div>
        </React.Fragment>
      ) : (
        <div className="mn-bulk-footnote">Converted leads are terminal and will be skipped (BR-BA-012).</div>
      )}
    </Modal>
  );
}

// ---------- Add / Remove Tags modal (BR-BA-016) ------------------------------
function BulkTagsModal({ scopeLabel, tagCatalog, selectionTags, initialMode, onClose, onConfirm }) {
  const [tagMode, setTagMode] = useStateBA(initialMode || 'add');
  const [tags, setTags] = useStateBA([]);
  const [removeSet, setRemoveSet] = useStateBA([]);
  const valid = tagMode === 'add' ? tags.length > 0 : removeSet.length > 0;
  const toggleRemove = (t) => setRemoveSet(a => a.includes(t) ? a.filter(x => x !== t) : [...a, t]);
  const footer = <React.Fragment>
    <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
    <Button variant="primary" size="md" icon="tag" disabled={!valid}
            onClick={() => onConfirm({ tagMode, tags: tagMode === 'add' ? tags : removeSet })}>
      {tagMode === 'add' ? 'Add tags' : 'Remove tags'}
    </Button>
  </React.Fragment>;
  return (
    <Modal open onClose={onClose} size="sm" icon="tag" iconTone="primary"
           eyebrow={'Bulk action · ' + scopeLabel} title={tagMode === 'add' ? 'Add tags' : 'Remove tags'}
           subtitle="Duplicates are never created; removing an absent tag is a no-op." footer={footer}>
      <SegControl value={tagMode} onChange={setTagMode} options={[{ value: 'add', label: 'Add tags' }, { value: 'remove', label: 'Remove tags' }]} />
      <div style={{ marginTop: 14 }}>
        {tagMode === 'add' ? (
          <Field label="Tags to add" required>
            <TagInput tags={tags} onChange={setTags} suggestions={tagCatalog} placeholder="Type a tag…" />
          </Field>
        ) : (
          <Field label="Tags present on the selection" required>
            {selectionTags.length ? (
              <div className="mn-bulk-taglist">
                {selectionTags.map(t => (
                  <label key={t.value} className="mn-check-row" style={{ padding: '3px 0' }}>
                    <input type="checkbox" className="mn-checkbox" checked={removeSet.includes(t.value)} onChange={() => toggleRemove(t.value)} />
                    <span className="mn-chip mn-chip--md mn-chip--tag">{t.value}</span>
                    <span style={{ flex: 1 }} />
                    <span className="mn-mono mn-muted" style={{ fontSize: 11 }}>{t.count}</span>
                  </label>
                ))}
              </div>
            ) : <div className="mn-bulk-footnote" style={{ marginTop: 0 }}>No tags on the selected leads.</div>}
          </Field>
        )}
      </div>
      <div className="mn-bulk-footnote">Audit is written only for actual changes (BR-BA-016).</div>
    </Modal>
  );
}

// ---------- Archive confirmation (BR-BA-017) ---------------------------------
function BulkArchiveModal({ count, scopeLabel, onClose, onConfirm }) {
  return (
    <Modal open onClose={onClose} size="sm" icon="archive" iconTone="warning"
           eyebrow={'Bulk action · ' + scopeLabel} title={'Archive ' + fmtCount(count) + ' lead' + (count === 1 ? '' : 's') + '?'}
           subtitle="Soft archive — leads move to the Archived view and can be restored."
           footer={<React.Fragment>
             <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
             <Button variant="destructive" size="md" icon="archive" onClick={onConfirm}>Archive leads</Button>
           </React.Fragment>}>
      <div style={{ font: 'var(--font-weight-default) 14px/22px var(--font-sans)', color: 'var(--text-1)' }}>
        Archived leads become read-only and leave all active views. Leads with active linked deals will require an explicit decision on the next step — nothing is cancelled silently.
      </div>
    </Modal>
  );
}

// ---------- Blocking modal: Disqualify / Archive with active Deals (BR-BA-015)
function BulkDealsBlockModal({ action, affected, wonBlocked, onClose, onContinue }) {
  const verb = action === 'disqualify' ? 'Disqualify' : 'Archive';
  const allDeals = affected.flatMap(x => x.deals);
  const total = allDeals.reduce((s, d) => s + (d.value || 0), 0);
  const footer = <React.Fragment>
    <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
    <Button variant="destructive" size="md" icon="ban" onClick={onContinue}>Cancel deals &amp; continue</Button>
  </React.Fragment>;
  return (
    <Modal open onClose={onClose} size="md" icon="alert-triangle" iconTone="error"
           eyebrow={'Bulk ' + verb.toLowerCase()} title={verb + ' blocked by active deals'}
           subtitle={`${affected.length} lead${affected.length > 1 ? 's have' : ' has'} ${allDeals.length} active deal${allDeals.length > 1 ? 's' : ''} · €${fmtCount(total)} total affected`}
           footer={footer}>
      <div className="mn-robanner" style={{ marginTop: 0 }}>
        <Icon name="alert-triangle" size="sm" />Continuing will <b style={{ margin: '0 4px' }}>cancel</b> these deals atomically with each lead. No silent cancellation — this is your explicit decision.
      </div>
      <div className="mn-bulk-dealtable">
        {affected.map(x => x.deals.map(d => (
          <div key={d.id} className="mn-row mn-bulk-dealrow">
            <span className="mn-row" style={{ gap: 8, minWidth: 0 }}>
              <DealStageChip stage={d.stage} />
              <span style={{ font: 'var(--font-weight-medium) 13px/16px var(--font-sans)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.name}</span>
            </span>
            <span className="mn-mono" style={{ fontSize: 12, color: 'var(--text-2)', flexShrink: 0 }}>{fmtMoney(d) || '—'} · {d.owner ? d.owner.name : '—'}</span>
          </div>
        )))}
      </div>
      {wonBlocked && wonBlocked.length ? (
        <div className="mn-robanner" style={{ marginTop: 12 }}>
          <Icon name="lock" size="sm" />{wonBlocked.length} lead{wonBlocked.length > 1 ? 's' : ''} with <b style={{ margin: '0 4px' }}>Won</b> linked deals will be skipped — Won deals require manual Admin resolution.
        </div>
      ) : null}
      <div className="mn-bulk-footnote">Lost deals stay Lost. Audit events share one <span className="mn-mono">batch_id</span> (K11 + K12); cancelling writes <span className="mn-mono">lead.disqualify_cancelled_by_user</span> (K13).</div>
    </Modal>
  );
}

// ---------- Export modal (BR-BA-018/019) -------------------------------------
function BulkExportModal({ count, scopeLabel, fields, defaultChecked, role, onClose, onConfirm }) {
  // BR-BA-019 treatments by permission: without leads.export.sensitive → sensitive fields (Phone/Email)
  // are HIDDEN from the field set entirely. With it → masked by default; full values require an
  // explicit elevated-permission acknowledgement (audited). Lead Current Time stays excluded (blocked).
  const canSensitive = window.MonolitRBAC.can(role, 'leads.export.sensitive');
  const visibleFields = canSensitive ? fields : fields.filter(f => !f.sensitive);
  const hiddenSensitive = canSensitive ? [] : fields.filter(f => f.sensitive);
  const [checked, setChecked] = useStateBA(defaultChecked.filter(k => visibleFields.some(f => f.k === k && !f.blocked)));
  const [format, setFormat] = useStateBA('csv');
  const [delivery, setDelivery] = useStateBA('download');
  const [sensitiveMode, setSensitiveMode] = useStateBA('masked'); // 'masked' | 'full'
  const [elevated, setElevated] = useStateBA(false);
  const toggle = (k) => setChecked(a => a.includes(k) ? a.filter(x => x !== k) : [...a, k]);
  const sensitiveOn = visibleFields.filter(f => f.sensitive && checked.includes(f.k));
  const needsElevation = sensitiveOn.length > 0 && sensitiveMode === 'full';
  const blockedByGovernance = needsElevation && !elevated;
  const maskSensitive = sensitiveOn.length > 0 && sensitiveMode === 'masked';
  const big = count > 10000;
  const footer = <React.Fragment>
    <Button variant="ghost" size="md" onClick={onClose}>Cancel</Button>
    <Button variant="primary" size="md" icon="download" disabled={!checked.length || blockedByGovernance}
            onClick={() => onConfirm({ fields: checked, format, delivery: big ? 'email' : delivery, maskSensitive })}>
      {big || delivery === 'email' ? 'Start export job' : 'Export ' + fmtCount(count)}
    </Button>
  </React.Fragment>;
  return (
    <Modal open onClose={onClose} size="md" icon="download" iconTone="primary"
           eyebrow="Bulk action · Export" title={'Export ' + fmtCount(count) + ' lead' + (count === 1 ? '' : 's')}
           subtitle={'Scope: ' + scopeLabel + ' · snapshot at export start (BR-BA-020)'} footer={footer}>
      <div className="mn-form-grid" style={{ gridTemplateColumns: '1fr 1fr', gap: 16 }}>
        <Field label="File format">
          <RadioGroup name="mn-exp-format" value={format} onChange={setFormat} options={[
            { value: 'csv', label: 'CSV', hint: 'Required in MVP' },
            { value: 'xlsx', label: 'XLSX', hint: 'Excel workbook' },
          ]} />
        </Field>
        <Field label="Delivery">
          <RadioGroup name="mn-exp-delivery" value={big ? 'email' : delivery} onChange={big ? () => {} : setDelivery} options={[
            { value: 'download', label: 'Direct download', hint: big ? 'Unavailable over 10 000 records' : 'Starts immediately' },
            { value: 'email', label: 'Email me a link', hint: 'Runs as a job · link expires' },
          ]} />
        </Field>
      </div>
      {big ? <div className="mn-robanner" style={{ marginTop: 12 }}><Icon name="clock" size="sm" />Over 10 000 records — the export always runs async; you will get an email + a download link in Operations.</div> : null}
      <div style={{ marginTop: 14 }}>
        <Field label={'Field set (' + checked.length + ' of ' + visibleFields.filter(f => !f.blocked).length + ') — follows current column order'}>
          <div className="mn-bulk-fieldgrid">
            {visibleFields.map(f => (
              <label key={f.k} className={'mn-check-row' + (f.blocked ? ' mn-bulk-field--blocked' : '')} style={{ padding: '2px 0' }} title={f.blocked || undefined}>
                <input type="checkbox" className="mn-checkbox" disabled={!!f.blocked} checked={!f.blocked && checked.includes(f.k)} onChange={() => toggle(f.k)} />
                <span className="mn-check-lbl">{f.label}{f.sensitive ? <Icon name="shield-alert" size="xs" color="warning" /> : null}{f.blocked ? <span className="mn-bulk-field-note"> not exportable</span> : null}</span>
              </label>
            ))}
          </div>
        </Field>
        {hiddenSensitive.length ? (
          <div className="mn-bulk-footnote" style={{ marginTop: 8, display: 'flex', gap: 6, alignItems: 'center' }}>
            <Icon name="eye-off" size="xs" color="text-3" />{hiddenSensitive.map(f => f.label).join(' & ')} hidden — sensitive export needs elevated permission (leads.export.sensitive) · BR-BA-019.
          </div>
        ) : null}
      </div>
      {sensitiveOn.length ? (
        <div style={{ marginTop: 14, border: '1px solid color-mix(in srgb, var(--warning-main) 35%, transparent)', background: 'var(--warning-low)', borderRadius: 'var(--radi-md)', padding: 12 }}>
          <div className="mn-row" style={{ gap: 8, marginBottom: 8 }}>
            <Icon name="shield-alert" size="sm" color="warning" />
            <span style={{ font: 'var(--font-weight-strong) 12px/16px var(--font-sans)', color: 'var(--warning-low-on)' }}>Sensitive field governance (BR-BA-019)</span>
          </div>
          <div style={{ font: 'var(--font-weight-medium) 12px/17px var(--font-sans)', color: 'var(--warning-low-on)', marginBottom: 10 }}>
            {sensitiveOn.map(f => f.label).join(' & ')} {sensitiveOn.length > 1 ? 'are' : 'is'} sensitive. Choose how {sensitiveOn.length > 1 ? 'they are' : 'it is'} exported:
          </div>
          <RadioGroup name="mn-exp-sensitive" value={sensitiveMode} onChange={(v) => { setSensitiveMode(v); if (v === 'masked') setElevated(false); }} options={[
            { value: 'masked', label: 'Masked', hint: 'e.g. j•••@•••.com · +•• ••• ••01 — no extra permission' },
            { value: 'full', label: 'Full values', hint: 'Elevated permission required (leads.export.sensitive)' },
          ]} />
          {needsElevation ? (
            <div style={{ marginTop: 10, paddingTop: 10, borderTop: '1px solid color-mix(in srgb, var(--warning-main) 25%, transparent)' }}>
              <Checkbox checked={elevated} onChange={setElevated}
                        label="I have elevated permission to export unmasked sensitive data"
                        hint="Recorded on the audit event lead.exported with actor, scope and field set." />
            </div>
          ) : null}
          <div className="mn-bulk-footnote" style={{ marginTop: 10 }}>Audited as <span className="mn-mono">lead.exported</span> with actor, scope, field set{maskSensitive ? ' and masking=on' : ' and elevated=granted'}.</div>
        </div>
      ) : null}
    </Modal>
  );
}

// Export field catalog — exportable business fields only; system columns and
// Lead Current Time are excluded/blocked per BR-BA-019.
const BULK_EXPORT_FIELDS = [
  { k: 'id', label: 'Lead ID', get: (l) => window.MonolitData.publicId(l.id) },
  { k: 'name', label: 'Lead Name', get: (l) => l.name },
  { k: 'company', label: 'Company', get: (l) => l.company || '' },
  { k: 'status', label: 'Status', get: (l) => (STATUS_CFG[l.status] ? STATUS_CFG[l.status].label : l.status) },
  { k: 'owner', label: 'Owner', get: (l) => (l.owner ? l.owner.name : '') },
  { k: 'score', label: 'Score', get: (l) => (l.score == null ? '' : l.score) },
  { k: 'phone', label: 'Phone', sensitive: true, get: (l) => l.phone || '' },
  { k: 'email', label: 'Email', sensitive: true, get: (l) => l.email || '' },
  { k: 'sourceType', label: 'Source Type', get: (l) => (l.source ? l.source.type : '') },
  { k: 'sourceName', label: 'Source Name', get: (l) => (l.source ? l.source.name : '') },
  { k: 'tags', label: 'Tags', get: (l) => (l.tags || []).join('|') },
  { k: 'nextStep', label: 'Next Step', get: (l) => (l.nextStep ? l.nextStep.text : '') },
  { k: 'lastActivity', label: 'Last Activity Date', get: (l) => { try { return new Date(l.lastActivity || l.updatedAt).toISOString().slice(0, 10); } catch (e) { return ''; } } },
  { k: 'createdAt', label: 'Created Date', get: (l) => { try { return new Date(l.createdAt).toISOString().slice(0, 10); } catch (e) { return ''; } } },
  { k: 'city', label: 'City', get: (l) => l.city || '' },
  { k: 'country', label: 'Country', get: (l) => l.country || '' },
  { k: 'leadTime', label: 'Lead Current Time', blocked: 'Not exportable by default in MVP (BR-BA-019)' },
];

// Mask helpers for Sensitive Export Governance (BR-BA-019) — keep just enough to correlate a row
// without exposing the full PII value.
function maskEmailValue(v) {
  const s = String(v || ''); const at = s.indexOf('@'); if (at < 1) return s ? '•••' : '';
  const dot = s.lastIndexOf('.'); const tld = dot > at ? s.slice(dot) : '';
  return s[0] + '•••@•••' + tld;
}
function maskPhoneValue(v) {
  const s = String(v || '').replace(/[^\d+]/g, ''); if (!s) return '';
  const tail = s.slice(-2);
  return (s[0] === '+' ? '+•• ' : '') + '••• ••' + tail;
}
// Build the export file from a snapshot of rows + chosen field keys (ordered).
// maskSensitive masks sensitive fields (Phone/Email) per BR-BA-019 when full-value export
// was not elevated.
function buildLeadsExportFile(rows, fieldKeys, format, maskSensitive) {
  const fields = fieldKeys.map(k => BULK_EXPORT_FIELDS.find(f => f.k === k)).filter(f => f && !f.blocked);
  const valueOf = (f, l) => {
    const raw = f.get(l);
    if (maskSensitive && f.sensitive) return f.k === 'email' ? maskEmailValue(raw) : f.k === 'phone' ? maskPhoneValue(raw) : '•••';
    return raw;
  };
  if (format === 'xlsx') {
    const cell = (v) => '<td>' + String(v == null ? '' : v).replace(/&/g, '&amp;').replace(/</g, '&lt;') + '</td>';
    const html = '<html xmlns:x="urn:schemas-microsoft-com:office:excel"><head><meta charset="utf-8"></head><body><table><tr>' +
      fields.map(f => cell(f.label)).join('') + '</tr>' +
      rows.map(l => '<tr>' + fields.map(f => cell(valueOf(f, l))).join('') + '</tr>').join('') + '</table></body></html>';
    return { name: 'leads-export.xls', mime: 'application/vnd.ms-excel', content: html };
  }
  const esc = (v) => { const s = String(v == null ? '' : v); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; };
  const csv = [fields.map(f => f.k).join(',')].concat(rows.map(l => fields.map(f => esc(valueOf(f, l))).join(','))).join('\n');
  return { name: 'leads-export.csv', mime: 'text/csv;charset=utf-8;', content: csv };
}

function mnDownloadFile(file) {
  const blob = new Blob([file.content], { type: file.mime });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href = url; a.download = file.name;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// ---------- Jobs / Operations panel (BR-BA-008/009) --------------------------
const JOB_STATE_CFG = {
  running:   { label: 'Running',   tone: 'primary', icon: 'loader' },
  retrying:  { label: 'Retrying',  tone: 'warning', icon: 'refresh-cw' },
  completed: { label: 'Completed', tone: 'success', icon: 'check-circle' },
  partial:   { label: 'Partial',   tone: 'warning', icon: 'alert-triangle' },
  failed:    { label: 'Failed',    tone: 'error',   icon: 'x-circle' },
};

function JobRow({ job, onDismiss }) {
  const [showFails, setShowFails] = useStateBA(false);
  const cfg = JOB_STATE_CFG[job.state] || JOB_STATE_CFG.running;
  const done = job.state === 'completed' || job.state === 'partial' || job.state === 'failed';
  const pct = job.total ? Math.round((job.processed / job.total) * 100) : 0;
  return (
    <div className="mn-job">
      <div className="mn-row" style={{ justifyContent: 'space-between', gap: 8 }}>
        <span style={{ font: 'var(--font-weight-medium) 13px/17px var(--font-sans)', color: 'var(--text-1)', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{job.label}</span>
        <span className="mn-row" style={{ gap: 6, flexShrink: 0 }}>
          <span className={'mn-chip mn-chip--md mn-job-state--' + cfg.tone}><Icon name={cfg.icon} size="xs" stroke="2" />{cfg.label}</span>
          {done ? <IconButton icon="x" size="sm" title="Dismiss" onClick={() => onDismiss(job.id)} /> : null}
        </span>
      </div>
      <div className="mn-progress" role="progressbar" aria-valuenow={pct} aria-valuemin="0" aria-valuemax="100">
        <div className={'mn-progress-b' + (job.state === 'retrying' ? ' mn-progress-b--retry' : '')} style={{ width: pct + '%' }} />
      </div>
      <div className="mn-job-meta">
        <span>{fmtCount(job.processed)}/{fmtCount(job.total)} processed</span>
        {job.failed ? <button type="button" className="mn-job-faillink" onClick={() => setShowFails(s => !s)}>{job.failed} failed{showFails ? ' ▴' : ' ▾'}</button> : null}
        {!done && job.etaMs != null ? <span>~{Math.max(1, Math.ceil(job.etaMs / 1000))}s left</span> : null}
        {job.retries ? <span className="mn-job-retry"><Icon name="refresh-cw" size="xs" />retry {job.retries}/3 · transient</span> : null}
        {done && job.file ? <button type="button" className="mn-login-link" style={{ fontSize: 12 }} onClick={() => mnDownloadFile(job.file)}>Download</button> : null}
      </div>
      {showFails && job.failures && job.failures.length ? (
        <div className="mn-job-fails">
          {job.failures.slice(0, 8).map(f => (
            <div key={f.id} className="mn-job-failrow">
              <span className="mn-job-failname">{f.name}</span>
              <span className="mn-job-failwhy">{f.reason}</span>
              <span className="mn-chip mn-chip--md mn-job-failcat">{f.category}</span>
            </div>
          ))}
          {job.failures.length > 8 ? <div className="mn-bulk-footnote" style={{ marginTop: 4 }}>+{job.failures.length - 8} more in the full report</div> : null}
        </div>
      ) : null}
    </div>
  );
}

function JobsPanel({ jobs, open, onToggle, onDismiss }) {
  if (!jobs.length) return null;
  const active = jobs.filter(j => j.state === 'running' || j.state === 'retrying').length;
  if (!open) {
    return (
      <button type="button" className="mn-jobs-pill" onClick={() => onToggle(true)} aria-label="Open operations panel">
        <Icon name={active ? 'loader' : 'check-circle'} size="sm" />
        {active ? active + ' running' : 'Operations'}
      </button>
    );
  }
  return (
    <div className="mn-jobs" role="region" aria-label="Operations">
      <div className="mn-jobs-h">
        <span className="mn-row" style={{ gap: 8 }}>
          <Icon name="list-checks" size="sm" />
          <span style={{ font: 'var(--font-weight-medium) 13px/17px var(--font-sans)' }}>Operations</span>
          {active ? <span className="mn-mono mn-muted" style={{ fontSize: 11 }}>{active} running</span> : null}
        </span>
        <IconButton icon="minus" size="sm" title="Minimize — jobs keep running" onClick={() => onToggle(false)} />
      </div>
      <div className="mn-jobs-list">
        {jobs.map(j => <JobRow key={j.id} job={j} onDismiss={onDismiss} />)}
      </div>
      <div className="mn-jobs-foot">You can close this panel and keep working — jobs continue in the background.</div>
    </div>
  );
}

Object.assign(window, {
  BulkBar, BulkLockBanner, BulkAssignModal, BulkStatusModal, BulkTagsModal, BulkArchiveModal,
  BulkDealsBlockModal, BulkExportModal, JobsPanel, BULK_EXPORT_FIELDS, buildLeadsExportFile, mnDownloadFile, fmtCount,
});
