// ── Org (company) Telegram connector — live data layer ──────────────────────
// Third real connector on the org console. Telegram has no OAuth, so connect
// is a phone-code login driven right here in the card (no popup): send-code →
// verify-code → (if the account has 2FA) verify-password. On success the
// backend stores the MTProto session on the ORG account and the connector
// reports connected through the same generic org surface Slack/Notion use:
//
//   POST   /org/connectors/telegram/login/send-code       → { phase: 'code_required' }
//   POST   /org/connectors/telegram/login/verify-code     → { phase: 'password_required' | 'connected' }
//   POST   /org/connectors/telegram/login/verify-password → { phase: 'connected' }
//   GET    /org/connectors/telegram/connection            → { is_active, status, sync_job }
//   POST   /org/connectors/telegram/sync                  → JobResponse (parks at awaiting_selection)
//   GET    /org/connectors/telegram/sync-items?job_id=…   → chat picker list
//   POST   /org/connectors/telegram/sync/{job}/ingest     → ingest the selected chats
//   DELETE /org/connectors/telegram/connection            → log the session out + clear record
//   DELETE /org/connectors/telegram/data                  → purge ingested chats
//
// The signed-in console session IS the org account (org-only console), so no
// target_account_id is sent. Unlike the OAuth connectors there is no backend
// callback to auto-kick the first sync, so the hook submits one itself the
// moment the login completes — the card flows straight into the chat picker.

const OT_SYNC_ITEMS_LIMIT = 500;      // server clamps to 500 — one page covers the chat list
const OT_JOB_POLL_MS = 2500;          // poll cadence while a sync/ingest job runs
// Telegram's connection status is a local record read (like Slack's, no
// provider round-trip), so the idle poll can match Slack's cadence.
const OT_IDLE_POLL_MS = 10_000;
// A failed sync-items read while the job sits at awaiting_selection would
// otherwise strand the card (no picker, and the connected-state buttons are
// hidden while awaiting) — retry on this cadence until the manifest loads.
const OT_ITEMS_RETRY_MS = 5000;

// Announced whenever the Telegram connection itself is made or dropped, so
// other live surfaces on the Connectors page (the hero stats strip) reflect it
// at once instead of waiting out their own poll.
const OT_CHANGED_EVENT = 'parcle:telegram-changed';

function otAnnounceChange() {
  try { window.dispatchEvent(new CustomEvent(OT_CHANGED_EVENT)); }
  catch (e) { /* very old browser — the listeners' polls still catch up */ }
}

const OrgTelegramAPI = {
  sendCode(phone)          { return osApi('POST', '/org/connectors/telegram/login/send-code', { phone }); },
  verifyCode(code)         { return osApi('POST', '/org/connectors/telegram/login/verify-code', { code }); },
  verifyPassword(password) { return osApi('POST', '/org/connectors/telegram/login/verify-password', { password }); },
  status()   { return osApi('GET', '/org/connectors/telegram/connection'); },
  sync()     { return osApi('POST', '/org/connectors/telegram/sync', { settings: {}, auto_ingest: false }); },
  syncItems(jobId) {
    const params = new URLSearchParams({ limit: String(OT_SYNC_ITEMS_LIMIT), offset: '0' });
    if (jobId) params.set('job_id', String(jobId));
    return osApi('GET', `/org/connectors/telegram/sync-items?${params.toString()}`);
  },
  ingest(jobId, { itemIds = null, selectAll = false, exclude = null } = {}) {
    return osApi('POST', `/org/connectors/telegram/sync/${encodeURIComponent(jobId)}/ingest`,
      { item_ids: itemIds, select_all: selectAll, exclude });
  },
  disconnect() { return osApi('DELETE', '/org/connectors/telegram/connection'); },
  purgeData()  { return osApi('DELETE', '/org/connectors/telegram/data'); },
};

// ── Hook ────────────────────────────────────────────────────────────────────
// Single state object the card renders from, mirroring useOrgSlack/useOrgNotion:
//   phase: 'loading' | 'disconnected' | 'connecting' | 'connected'
//   loginStep: while connecting — 'phone' | 'code' | 'password' (which input
//              the inline login form should show)
//   syncJob: latest JobResponse (or null) — status/progress/payload.counts
//   items / itemsJobId: chat rows for the picker (awaiting_selection)
function useOrgTelegram() {
  const { useState, useEffect, useRef, useCallback } = React;
  const [phase, setPhase] = useState('loading');
  const [loginStep, setLoginStep] = useState('phone');
  const [syncJob, setSyncJob] = useState(null);
  const [items, setItems] = useState(null);
  const [itemsJobId, setItemsJobId] = useState(null);
  // Bumped after a failed sync-items read to re-run the loader effect.
  const [itemsRetry, setItemsRetry] = useState(0);
  const [error, setError] = useState(null);
  const alive = useRef(true);
  // While the phone-code login is underway the backend records the in-progress
  // login with the connection DISABLED, so the status poll reads inactive — it
  // must not flip the card out of its 'connecting' state mid-login.
  const connecting = useRef(false);
  useEffect(() => () => { alive.current = false; }, []);

  const refresh = useCallback(async () => {
    const s = await OrgTelegramAPI.status();
    if (!alive.current) return null;
    if (s.is_active) { if (!connecting.current) setPhase('connected'); }
    else if (!connecting.current) setPhase('disconnected');
    setSyncJob(s.sync_job || null);
    return s;
  }, []);

  // Initial load + poll while a job is running (discover/export/ingest all
  // report through the job the connection endpoint already returns).
  useEffect(() => {
    let timer = null;
    let stopped = false;
    const tick = async () => {
      if (stopped) return;
      let s = null;
      try { s = await refresh(); } catch (e) { if (alive.current) setError(String(e.message || e)); }
      if (stopped) return;
      const again = s && osJobActive(s.sync_job);
      timer = setTimeout(tick, again ? OT_JOB_POLL_MS : OT_IDLE_POLL_MS);
    };
    tick();
    return () => { stopped = true; if (timer) clearTimeout(timer); };
  }, [refresh]);

  // When the latest job parks at awaiting_selection, load its chat manifest.
  // A failed read schedules its own retry (see OT_ITEMS_RETRY_MS): without it
  // the effect's inputs never change again and the card would strand with an
  // error but no picker and no way out short of a page reload.
  useEffect(() => {
    const job = syncJob;
    if (!job || job.status !== 'awaiting_selection') return;
    if (itemsJobId === String(job.id) && items) return;
    let cancelled = false;
    let retryTimer = null;
    OrgTelegramAPI.syncItems(job.id).then(page => {
      if (cancelled || !alive.current) return;
      setItems(page.items || []);
      setItemsJobId(String(job.id));
    }).catch(e => {
      if (cancelled || !alive.current) return;
      setError(String(e.message || e));
      retryTimer = setTimeout(() => {
        if (!cancelled && alive.current) setItemsRetry(n => n + 1);
      }, OT_ITEMS_RETRY_MS);
    });
    return () => { cancelled = true; if (retryTimer) clearTimeout(retryTimer); };
  }, [syncJob && syncJob.id, syncJob && syncJob.status, itemsRetry]);

  const syncNow = useCallback(async () => {
    setError(null);
    try {
      const job = await OrgTelegramAPI.sync();
      if (!alive.current) return;
      setItems(null); setItemsJobId(null);
      setSyncJob(job);
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, []);

  // Login completed (verify-code without 2FA, or verify-password). No backend
  // callback exists to auto-kick the first sync the way OAuth connectors get,
  // so kick it here — the card flows straight into "Scanning chats…". A sync
  // submit failure must not mask the fact that the CONNECTION succeeded.
  const finishLogin = useCallback(async () => {
    connecting.current = false;
    setPhase('connected');
    otAnnounceChange();
    try {
      const job = await OrgTelegramAPI.sync();
      if (!alive.current) return;
      setItems(null); setItemsJobId(null);
      setSyncJob(job);
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, []);

  // Open the inline login form. Nothing is sent yet — sendCode does that.
  const beginConnect = useCallback(() => {
    setError(null);
    connecting.current = true;
    setLoginStep('phone');
    setPhase('connecting');
  }, []);

  const sendCode = useCallback(async (phone) => {
    setError(null);
    try {
      await OrgTelegramAPI.sendCode(phone);
      if (!alive.current || !connecting.current) return;
      setLoginStep('code');
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, []);

  const verifyCode = useCallback(async (code) => {
    setError(null);
    try {
      const r = await OrgTelegramAPI.verifyCode(code);
      if (!alive.current || !connecting.current) return;
      if (r.phase === 'password_required') setLoginStep('password');
      else await finishLogin();
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, [finishLogin]);

  const verifyPassword = useCallback(async (password) => {
    setError(null);
    try {
      await OrgTelegramAPI.verifyPassword(password);
      if (!alive.current || !connecting.current) return;
      await finishLogin();
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, [finishLogin]);

  // Abandon a half-done login. The backend keeps its in-progress login fields
  // (disabled connection) but they are inert — the next beginConnect/sendCode
  // simply overwrites them.
  const cancelConnect = useCallback(() => {
    if (!connecting.current) return;
    connecting.current = false;
    setError(null);
    setPhase('disconnected');
  }, []);

  const ingest = useCallback(async (selection) => {
    if (!syncJob) return;
    setError(null);
    try {
      const job = await OrgTelegramAPI.ingest(syncJob.id, selection);
      if (!alive.current) return;
      setItems(null); setItemsJobId(null);
      setSyncJob(job);
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, [syncJob && syncJob.id]);

  const disconnect = useCallback(async () => {
    setError(null);
    try {
      await OrgTelegramAPI.disconnect();
      if (!alive.current) return;
      setItems(null); setItemsJobId(null); setSyncJob(null);
      setPhase('disconnected');
      otAnnounceChange();
    } catch (e) { if (alive.current) setError(String(e.message || e)); }
  }, []);

  return {
    phase, loginStep, syncJob, items, error,
    beginConnect, sendCode, verifyCode, verifyPassword, cancelConnect,
    syncNow, ingest, disconnect, refresh,
  };
}

Object.assign(window, { OrgTelegramAPI, useOrgTelegram, OT_CHANGED_EVENT });
