// Databricks connector card — the prominent entry point on the Connectors page,
// rendered directly under the Slack card (see connectors.jsx). Mirrors
// OrgSlackCard's shape: logo, live status line, and the one action that makes
// sense right now.
//
// Division of labour with databricks_connector.jsx: this card owns CONNECTING
// (it must open the OAuth window synchronously from the click, or pop-up
// blockers reject it), the drawer owns picking warehouses/schemas and managing
// an existing connection. Once the connection lands we open the drawer, which
// takes the user straight to the schema picker.

const { useState: useOdb, useEffect: useOdbEffect, useRef: useOdbRef } = React;

const ODB_POLL_MS = 2000;
// Match OS_CONNECT_TIMEOUT_MS in org_slack_data.jsx — two cards on the same page
// waiting different lengths for the same kind of popup reads as a bug.
const ODB_CONNECT_TIMEOUT_MS = 180_000;
const ODB_STATUS_POLL_MS = 15000;
// Broadcast by the drawer whenever it mutates the connection, so the card
// doesn't have to wait out a poll interval to catch up.
const ODB_CHANGED_EVENT = 'parcle:databricks-changed';

function odbLive() {
  try { return !!(typeof parcleApiBase === 'function' && parcleApiBase()); }
  catch (e) { return false; }
}

// Status text and dot come from ONE ordered decision, so the dot can never
// contradict the text (e.g. a stale scan job leaving a spinning dot beside
// "Not connected"). Pure function — kept out of the component so it can be
// exercised directly. Returns [line, dotKind].
function odbStatus({ phase, needsReconnect, scanning, scanFailed, scanCancelled, schemaCount }) {
  if (phase === 'loading') return ['Checking status…', 'paused'];
  if (phase === 'unknown') return ["Can't reach Parcle — retrying…", 'paused'];
  if (phase === 'connecting') {
    return ['Complete the authorization in the Databricks window…', 'syncing'];
  }
  if (needsReconnect) return ['Authorization expired — reconnect', 'error'];
  if (phase === 'disconnected') return ['Not connected', 'paused'];
  if (scanning) return ['Scanning schemas into the knowledge graph…', 'syncing'];
  if (scanFailed) return ['Last schema scan failed', 'error'];
  if (scanCancelled) return ['Last schema scan cancelled', 'paused'];
  if (schemaCount) {
    return [
      `${schemaCount} schema${schemaCount === 1 ? '' : 's'} queryable by the agent`,
      'live',
    ];
  }
  return ['Connected — no schemas selected yet', 'live'];
}

function OrgDatabricksCard({ onManage }) {
  const [conn, setConn] = useOdb(null);
  // loading|disconnected|connecting|connected|unknown. 'unknown' means the last
  // status read FAILED — distinct from 'disconnected' (the backend answered and
  // said there is no connection), because showing "Not connected" after a blip
  // would invite the user to redo an OAuth they already completed.
  const [phase, setPhase] = useOdb('loading');
  const [scan, setScan] = useOdb(null);
  const [error, setError] = useOdb(null);
  const alive = useOdbRef(true);
  const pollRef = useOdbRef(null);
  const popupRef = useOdbRef(null);
  const rootRef = useOdbRef(null);

  const stopPoll = () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } };
  useOdbEffect(() => () => { alive.current = false; stopPoll(); }, []);

  const applyStatus = (c) => {
    setConn(c);
    setPhase(dbxIsConnected(c) ? 'connected' : 'disconnected');
  };

  const refresh = async () => {
    try {
      const c = await DatabricksAPI.connection();
      if (!alive.current) return null;
      setError(null);
      applyStatus(c);
      return c;
    } catch (err) {
      // Keep whatever we last knew; the poll below retries. Only the very first
      // read has nothing to fall back on, and that shows as "unknown".
      if (alive.current && phase === 'loading') setPhase('unknown');
      return null;
    }
  };

  const refreshScan = async (c) => {
    if (!dbxIsConnected(c)) return;
    try {
      const s = await DatabricksAPI.scanStatus();
      if (alive.current) setScan((s && s.id) ? s : (s && s.job) || null);
    } catch (e) { /* the status line just omits the scan */ }
  };

  const refreshAll = async () => { await refreshScan(await refresh()); };

  // Initial status + the scan job, so a returning user sees where things stand.
  useOdbEffect(() => { refreshAll(); }, []);

  // Poll in EVERY state except 'connecting' (which runs its own faster loop).
  // Polling only while connected would make a failed read unrecoverable: this
  // page is mounted once and hidden with display:none, never remounted, so
  // without a retry the card would stay wrong until a full page reload.
  useOdbEffect(() => {
    if (phase === 'connecting') return undefined;
    const t = setInterval(refreshAll, ODB_STATUS_POLL_MS);
    return () => clearInterval(t);
  }, [phase]);

  // The drawer is the other writer (connect / save selection / disconnect); it
  // announces changes so the card updates immediately instead of up to a poll
  // interval later.
  useOdbEffect(() => {
    const onChanged = () => { refreshAll(); };
    window.addEventListener(ODB_CHANGED_EVENT, onChanged);
    return () => window.removeEventListener(ODB_CHANGED_EVENT, onChanged);
  }, []);

  const connect = () => {
    setError(null);
    // When the server can't authorize on its own (no workspace host / client id
    // for this org yet) there is nothing to redirect to — hand off to the
    // drawer, which asks for them. Going ahead would just surface a raw 400.
    if (conn && conn.ready_to_authorize === false) {
      if (typeof onManage === 'function') onManage();
      return;
    }
    // Synchronous open inside the click handler — after an await the browser
    // treats it as an unrequested pop-up and blocks it.
    const popup = window.open('', 'parcle-databricks-auth-card', 'width=560,height=720');
    if (!popup) {
      setError('Your browser blocked the Databricks window — allow pop-ups for this site and try again.');
      return;
    }
    popupRef.current = popup;
    popup.document.write('<p style="font:14px system-ui;padding:24px">Opening Databricks…</p>');
    setPhase('connecting');
    (async () => {
      try {
        const r = await DatabricksAPI.connectStart();
        const url = r && (r.authorize_url || r.authorizeUrl);
        if (!url) throw new Error('Backend did not return an authorize URL');
        popup.location = url;
        const startedAt = Date.now();
        stopPoll();
        pollRef.current = setInterval(async () => {
          if (!alive.current) { stopPoll(); return; }
          if (Date.now() - startedAt > ODB_CONNECT_TIMEOUT_MS) {
            stopPoll();
            setError('Authorization timed out — try again.');
            setPhase('disconnected');
            return;
          }
          try {
            const c = await DatabricksAPI.connection();
            if (dbxIsConnected(c)) {
              stopPoll();
              applyStatus(c);
              // The drawer announces its own writes; this card is the other
              // place a connection can be made, so it must announce too or the
              // page's other live surfaces sit stale until their next poll.
              try { window.dispatchEvent(new CustomEvent(ODB_CHANGED_EVENT)); } catch (e) {}
              try { popup.close(); } catch (e) {}
              // Straight into the schema picker — but only if the user is still
              // looking at this page. The Connectors route stays mounted and
              // merely display:none'd when they navigate away, so without this
              // check a finished authorization would throw a modal over
              // whatever page they moved on to. offsetParent is null exactly
              // when an ancestor is display:none.
              const visible = rootRef.current && rootRef.current.offsetParent !== null;
              if (visible && typeof onManage === 'function') onManage();
            }
          } catch (e) { /* keep polling */ }
        }, ODB_POLL_MS);
      } catch (err) {
        try { popup.close(); } catch (e) {}
        if (!alive.current) return;
        setError(err.message || 'Could not start authorization');
        setPhase('disconnected');
      }
    })();
  };

  const cancelConnect = () => {
    stopPoll();
    try { if (popupRef.current) popupRef.current.close(); } catch (e) {}
    setPhase('disconnected');
  };

  const selection = (conn && conn.selection) || null;
  const schemaCount = selection && Array.isArray(selection.schemas) ? selection.schemas.length : 0;
  const scanning = scan && !['done', 'failed', 'cancelled'].includes(scan.status);
  const needsReconnect = dbxNeedsReconnect(conn);

  const [statusLine, dotKind] = odbStatus({
    phase,
    needsReconnect,
    scanning,
    scanFailed: !!(scan && scan.status === 'failed'),
    scanCancelled: !!(scan && scan.status === 'cancelled'),
    schemaCount,
  });

  return (
    <div ref={rootRef} className="card" style={{ padding: '18px 22px', marginTop: 24 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <VendorLogo vendor="databricks" size={36} radius={9}/>
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span className="t-h3">Databricks</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={dotKind}/>
            <span className="mono-sm mono text-secondary">{statusLine}</span>
          </div>
        </div>
        {phase === 'disconnected' && !needsReconnect && (
          <button className="btn accent" onClick={connect}>Connect Databricks</button>
        )}
        {needsReconnect && (
          <button className="btn accent" onClick={connect}>Reconnect</button>
        )}
        {phase === 'connecting' && (
          <button className="btn ghost" onClick={cancelConnect}>Cancel</button>
        )}
        {phase === 'connected' && !needsReconnect && (
          <button className="btn" onClick={onManage}>
            {schemaCount ? 'Manage' : 'Choose schemas'}
          </button>
        )}
      </div>

      {phase === 'disconnected' && !needsReconnect && (
        <div className="text-secondary t-small" style={{ marginTop: 10 }}>
          Sign in to Databricks and approve read-only access, then pick the
          catalogs and schemas the agent may query. It writes SQL against them
          directly — nothing is copied out of your lakehouse.
        </div>
      )}

      {phase === 'connected' && schemaCount > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 12 }}>
          {selection.schemas.slice(0, 8).map(s => (
            <span key={`${s.catalog}.${s.schema}`} className="chip" style={{ padding: '2px 8px' }}>
              {s.catalog}.{s.schema}
            </span>
          ))}
          {schemaCount > 8 && (
            <span className="mono-sm mono text-tertiary">+{schemaCount - 8} more</span>
          )}
        </div>
      )}

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

function OrgDatabricksSection({ onManage }) {
  if (!odbLive()) return null;
  return <OrgDatabricksCard onManage={onManage}/>;
}

Object.assign(window, { OrgDatabricksSection, odbStatus, ODB_CHANGED_EVENT });
