// ── Org (company) Google connectors — live UI ───────────────────────────────
// Rendered by ConnectorsHub (org mode) when a live backend is configured.
// Drive, Gmail and Calendar are three cards over ONE shared Google connection
// (see org_google_data.jsx): Connect on any card → OAuth popup (Composio
// link) → the backend's callback auto-kicks that product's sync, which parks
// at awaiting_selection → pick items → ingest. Ingested items land in the
// org's memory (sources/chunks), where the knowledge agent's document search
// already reads them.

const { useState: useOgState, useMemo: useOgMemo } = React;

// "2.4 KB" / "1.3 MB" for the picker rows; Drive's listing carries sizes for
// binary files but not for native Google docs — render nothing then.
function ogFmtSize(bytes) {
  if (bytes == null || !isFinite(bytes)) return null;
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function OrgGoogleItemPicker({ items, itemsTotal, onIngest, busy, copy }) {
  // Only 'discovered' (new/changed) items are selectable. Already-ingested
  // ('unchanged') ones render as checked+disabled "In memory"; skipped ones
  // render unchecked+disabled with a Skipped chip.
  const selectable = useOgMemo(() => items.filter(i => i.status === 'discovered'), [items]);
  // Drive starts all-selected (team documents, curated by the sync itself);
  // Gmail/Calendar start EMPTY like the Telegram chat picker — a personal
  // mailbox/calendar becoming org knowledge must be an opt-in per item, not
  // an opt-out.
  const [picked, setPicked] = useOgState(
    () => new Set(copy.preselect ? selectable.map(i => i.item_id) : [])
  );
  const toggle = (id) => setPicked(prev => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });
  const allPicked = picked.size === selectable.length && selectable.length > 0;
  const toggleAll = () => setPicked(allPicked ? new Set() : new Set(selectable.map(i => i.item_id)));

  return (
    <div style={{ marginTop: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
        <div className="t-h3">Choose {copy.plural} to ingest</div>
        {selectable.length > 0 && (
          <button className="btn sm ghost" onClick={toggleAll} disabled={busy}>
            {allPicked ? 'Clear all' : 'Select all'}
          </button>
        )}
      </div>
      <div style={{ maxHeight: 280, overflowY: 'auto', border: '1px solid var(--border-subtle)', borderRadius: 8 }}>
        {items.length === 0 && (
          <div className="text-secondary t-small" style={{ padding: 14 }}>
            No {copy.plural} found in {copy.sourceName}.
          </div>
        )}
        {items.map(i => {
          const selectableRow = i.status === 'discovered';
          const inMemory = i.status === 'unchanged';
          const skipped = !selectableRow && !inMemory;
          const size = ogFmtSize(i.size_bytes);
          return (
            <label key={i.item_id} style={{
              display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
              borderBottom: '1px solid var(--border-subtle)', cursor: selectableRow ? 'pointer' : 'default',
              opacity: selectableRow ? 1 : 0.6,
            }}>
              <input type="checkbox" disabled={busy || !selectableRow}
                checked={inMemory ? true : picked.has(i.item_id)}
                onChange={() => toggle(i.item_id)}/>
              <span style={{ flex: 1, fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {i.item_name || i.item_id}
              </span>
              {size != null && (
                <span className="mono-sm mono text-tertiary">{size}</span>
              )}
              {inMemory && <span className="chip" style={{ fontSize: 10, height: 20 }}>In memory</span>}
              {skipped && (
                <span className="chip" style={{ fontSize: 10, height: 20 }}
                  title={i.skip_reason || i.status}>
                  {i.status === 'skipped_too_large' ? 'Too large' : 'Skipped'}
                </span>
              )}
            </label>
          );
        })}
      </div>
      {itemsTotal != null && itemsTotal > items.length && (
        <div className="mono-sm mono text-tertiary" style={{ marginTop: 6 }}>
          Showing {items.length} of {itemsTotal} {copy.plural} from this sync —
          the rest aren’t selectable here yet.
        </div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10 }}>
        {selectable.length === 0 ? (
          // Nothing selectable (everything already in memory or skipped): a
          // disabled "Ingest 0 items" reads as broken, so say what's up —
          // the card's Sync now / Disconnect buttons are the way out.
          items.length > 0 && (
            <span className="text-secondary t-small">
              {items.some(i => i.status !== 'unchanged')
                ? `No ${copy.plural} can be ingested — everything here is already in memory or was skipped.`
                : `No new ${copy.plural} to ingest — everything here is already in memory.`}
            </span>
          )
        ) : (
          <>
            <button className="btn accent" disabled={busy || picked.size === 0}
              onClick={() => onIngest({ itemIds: Array.from(picked) })}>
              Ingest {picked.size} {picked.size === 1 ? copy.singular : copy.plural}
            </button>
            <span className="mono-sm mono text-tertiary">
              {copy.capPlural} become searchable org knowledge.
            </span>
          </>
        )}
      </div>
    </div>
  );
}

function OrgGoogleSyncProgress({ job, copy }) {
  const payload = job.payload || {};
  const counts = payload.counts || {};
  const total = counts.total || 0;
  const done = (counts.done || 0) + (counts.failed || 0);
  const pct = typeof job.progress === 'number' ? Math.round(job.progress * 100)
    : (total > 0 ? Math.round((done / total) * 100) : 0);
  const phase = payload.phase || job.status;
  return (
    <div style={{ marginTop: 12 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
        <span className="mono-sm mono text-secondary">
          {phase === 'discovering' || job.status === 'pending' ? copy.scanning
            : `Ingesting ${copy.plural}… ${total ? `${done}/${total}` : ''}`}
        </span>
        <span className="mono-sm mono text-tertiary">{pct}%</span>
      </div>
      <Progress value={Math.max(2, pct)} height={4}/>
    </div>
  );
}

// One Google sign-in covers all three cards; each card's disconnect revokes
// that shared connection, so every card carries the same warning.
const OG_DISCONNECT_TITLE =
  'Disconnects the shared Google connection — Drive, Gmail and Calendar all disconnect. Already-ingested content stays in org memory.';

function OrgGoogleProductCard({ api, copy }) {
  const { phase, syncJob, items, itemsTotal, error, connect, cancelConnect, syncNow, ingest, disconnect } = useOrgGoogleProduct(api);
  const [busy, setBusy] = useOgState(false);
  const run = (fn) => async (...args) => {
    setBusy(true);
    try { await fn(...args); } finally { setBusy(false); }
  };

  const jobActive = syncJob && osJobActive(syncJob);
  const awaiting = phase === 'connected' && syncJob && syncJob.status === 'awaiting_selection';
  const failed = syncJob && syncJob.status === 'failed';
  const cancelled = syncJob && syncJob.status === 'cancelled';
  const donePayload = (syncJob && syncJob.status === 'done' && (syncJob.payload || {})) || null;
  const doneCounts = (donePayload && donePayload.counts) || null;
  // A sync that found nothing new/changed (the backend completes it as done
  // instead of parking an empty picker). "Skipped" discoveries also end here —
  // the counts tell the two apart.
  const nothingNew = !!(donePayload && donePayload.nothing_new);
  const nothingNewSkipped = nothingNew && doneCounts
    ? (doneCounts.skipped_too_large || 0) + (doneCounts.skipped_too_small || 0)
      + (doneCounts.skipped_unsupported || 0)
    : 0;
  // An awaiting_selection picker with nothing selectable (everything unchanged
  // or skipped) is a dead end — bring back the Sync now / Disconnect exits.
  // Only when the single fetched page IS the whole manifest, though: discovered
  // rows sort last server-side, so a truncated page full of unchanged rows says
  // nothing about whether new items exist beyond it — claiming "no new items"
  // there would be false.
  const emptyAwaiting = awaiting && !!items && !items.some(i => i.status === 'discovered')
    && (itemsTotal == null || itemsTotal <= items.length);

  const statusLine =
    phase === 'loading' ? 'Checking status…'
    : phase === 'connecting' ? 'Complete the authorization in the Google window…'
    : phase === 'disconnected' ? 'Not connected'
    : jobActive ? 'Syncing'
    : emptyAwaiting ? `No new ${copy.plural} to ingest`
    : awaiting ? `Choose ${copy.plural} to ingest`
    : failed ? 'Last sync failed'
    : cancelled ? 'Last sync cancelled'
    : nothingNew ? (nothingNewSkipped > 0
        ? `No ingestable ${copy.plural} — ${nothingNewSkipped} skipped`
        : `No new ${copy.plural} — everything already in memory`)
    : doneCounts ? `${doneCounts.done || 0} ${copy.plural} in memory`
    : 'Connected';

  return (
    <div id={copy.domId} className="card" style={{ padding: '18px 22px', marginTop: 24 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <VendorLogo vendor={copy.vendor} size={36} radius={9}/>
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span className="t-h3">{copy.title}</span>
            <span className="chip accent" style={{ fontSize: 10, height: 20 }}>Live</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 3 }}>
            <StatusDot size={6} kind={
              phase === 'connected' ? (jobActive ? 'syncing' : failed ? 'error' : cancelled ? 'paused' : 'live')
              : phase === 'connecting' ? 'syncing' : 'paused'
            }/>
            <span className="mono-sm mono text-secondary">{statusLine}</span>
          </div>
        </div>
        {phase === 'disconnected' && (
          <button className="btn accent" disabled={busy} onClick={run(connect)}>
            Connect {copy.title}
          </button>
        )}
        {phase === 'connecting' && (
          <button className="btn ghost" onClick={cancelConnect}>Cancel</button>
        )}
        {phase === 'connected' && !jobActive && (!awaiting || emptyAwaiting) && (
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn" disabled={busy} onClick={run(syncNow)}>Sync now</button>
            <button className="btn ghost" disabled={busy} onClick={run(disconnect)}
              title={OG_DISCONNECT_TITLE}>Disconnect</button>
          </div>
        )}
      </div>

      {phase === 'disconnected' && (
        <div className="text-secondary t-small" style={{ marginTop: 10 }}>
          {copy.description} One Google sign-in connects Drive, Gmail and
          Calendar together (and disconnecting any one of them disconnects all
          three), but each connector only ingests the {copy.plural} you
          explicitly select from this card.
        </div>
      )}

      {error && (
        <div className="t-small" style={{ marginTop: 10, color: 'var(--error)' }}>{error}</div>
      )}

      {jobActive && <OrgGoogleSyncProgress job={syncJob} copy={copy}/>}
      {awaiting && items && (
        <OrgGoogleItemPicker items={items} itemsTotal={itemsTotal} busy={busy}
          onIngest={run(ingest)} copy={copy}/>
      )}
      {failed && (
        <div style={{ marginTop: 10 }}>
          <button className="btn sm" disabled={busy} onClick={run(syncNow)}>Retry sync</button>
        </div>
      )}
    </div>
  );
}

// Per-product copy. `description` is the disconnected-state pitch; the shared
// one-sign-in sentence is appended by the card.
const OG_DRIVE_COPY = {
  domId: 'org-googledrive-connector', vendor: 'cloudbin', title: 'Google Drive',
  singular: 'file', plural: 'files', capPlural: 'Files',
  sourceName: 'this Drive', scanning: 'Scanning Drive…', preselect: true,
  description: 'Authorize with the Google account whose Drive files should '
    + 'become org knowledge — documents, spreadsheets and files it can read '
    + 'are offered for ingestion.',
};
const OG_GMAIL_COPY = {
  domId: 'org-gmail-connector', vendor: 'gmail', title: 'Gmail',
  singular: 'email', plural: 'emails', capPlural: 'Emails',
  sourceName: 'this mailbox', scanning: 'Scanning mailbox…', preselect: false,
  description: 'Authorize with the Google account whose emails should become '
    + 'org knowledge. This is a member’s personal mailbox — every email '
    + 'you ingest becomes searchable by the whole org, so select carefully.',
};
const OG_GCAL_COPY = {
  domId: 'org-gcalendar-connector', vendor: 'gcal', title: 'Google Calendar',
  singular: 'event', plural: 'events', capPlural: 'Events',
  sourceName: 'this calendar', scanning: 'Scanning calendar…', preselect: false,
  description: 'Authorize with the Google account whose calendar events should '
    + 'become org knowledge. This is a member’s personal calendar — every '
    + 'event you ingest becomes searchable by the whole org, so select carefully.',
};

// Section wrappers ConnectorsHub renders: only exist on a live backend (mock
// demo mode keeps the page unchanged).
function OrgGoogleDriveSection() {
  if (!osLive()) return null;
  return <OrgGoogleProductCard api={OrgGoogleDriveAPI} copy={OG_DRIVE_COPY}/>;
}
function OrgGmailSection() {
  if (!osLive()) return null;
  return <OrgGoogleProductCard api={OrgGmailAPI} copy={OG_GMAIL_COPY}/>;
}
function OrgGCalendarSection() {
  if (!osLive()) return null;
  return <OrgGoogleProductCard api={OrgGCalendarAPI} copy={OG_GCAL_COPY}/>;
}

Object.assign(window, { OrgGoogleDriveSection, OrgGmailSection, OrgGCalendarSection });
