/* Dump — connect.jsx: integrations + suggestion cards.
   In demo mode every source is simulated end-to-end:
   fake OAuth → staged scans → persona data (see demo-data.js). */

/* ---------- Google helper (real Calendar + Gmail via GIS) ---------- */
const GoogleHelper = {
  tokenClient: null,

  loadGIS() {
    return new Promise((resolve, reject) => {
      if (window.google && window.google.accounts) return resolve();
      const s = document.createElement('script');
      s.src = 'https://accounts.google.com/gsi/client';
      s.onload = () => resolve();
      s.onerror = () => reject(new Error("Couldn't load Google sign-in — check your connection."));
      document.head.appendChild(s);
    });
  },

  async connect() {
    // Simulated connections: fake OAuth, persona data
    if (DumpAPI.simConnections()) {
      const ok = await AuthSim.open('google');
      if (!ok) return false;
      DumpStore.set((s) => ({
        google: {
          ...s.google, connected: true, demo: true, token: null,
          email: DumpDemo.persona.email,
          events: DumpDemo.calendarEvents,
          eventsDate: DumpUtil.todayISO()
        }
      }));
      DumpAPI.toast('Connected as ' + DumpDemo.persona.email);
      return true;
    }
    const st = DumpStore.getState();
    const clientId = (st.settings.googleClientId || '').trim();
    if (!clientId) {
      DumpAPI.toast('Add your Google Client ID under Settings first.');
      DumpStore.set({ settingsOpen: true });
      return;
    }
    try {
      await this.loadGIS();
    } catch (e) { DumpAPI.toast(e.message); return; }
    return new Promise((resolve) => {
      this.tokenClient = google.accounts.oauth2.initTokenClient({
        client_id: clientId,
        scope: 'https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/gmail.readonly',
        callback: async (resp) => {
          if (resp.error) {
            DumpAPI.toast('Google said no — check your Client ID and authorised origins.');
            return resolve(false);
          }
          DumpStore.set((s) => ({ google: { ...s.google, connected: true, demo: false, token: resp.access_token } }));
          DumpAPI.toast('Google connected.');
          await GoogleHelper.fetchTodayEvents();
          resolve(true);
        }
      });
      this.tokenClient.requestAccessToken();
    });
  },

  disconnect() {
    const g = DumpStore.getState().google;
    if (g.token && window.google && google.accounts.oauth2.revoke) {
      try { google.accounts.oauth2.revoke(g.token, () => {}); } catch (e) {}
    }
    DumpStore.set({ google: { connected: false, demo: false, token: null, email: null, events: [], eventsDate: null } });
    DumpAPI.toast('Google disconnected.');
  },

  async gFetch(url, opts) {
    const tok = DumpStore.getState().google.token;
    const res = await fetch(url, Object.assign({}, opts, {
      headers: Object.assign({ Authorization: 'Bearer ' + tok }, (opts && opts.headers) || {})
    }));
    if (res.status === 401) {
      DumpStore.set((s) => ({ google: { ...s.google, connected: false, token: null } }));
      throw new Error('Google session expired — connect again.');
    }
    if (!res.ok) throw new Error('Google replied with an error — try again in a moment.');
    return res.json();
  },

  async fetchTodayEvents() {
    const g = DumpStore.getState().google;
    if (g.demo) {
      DumpStore.set((s) => ({ google: { ...s.google, events: DumpDemo.calendarEvents, eventsDate: DumpUtil.todayISO() } }));
      return;
    }
    try {
      const start = new Date(); start.setHours(0, 0, 0, 0);
      const end = new Date(); end.setHours(23, 59, 59, 999);
      const data = await this.gFetch(
        'https://www.googleapis.com/calendar/v3/calendars/primary/events?singleEvents=true&orderBy=startTime' +
        '&timeMin=' + encodeURIComponent(start.toISOString()) +
        '&timeMax=' + encodeURIComponent(end.toISOString())
      );
      const events = (data.items || []).map((ev) => ({
        time: ev.start && ev.start.dateTime
          ? new Date(ev.start.dateTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
          : 'All day',
        title: ev.summary || '(untitled)'
      }));
      DumpStore.set((s) => ({ google: { ...s.google, events, eventsDate: DumpUtil.todayISO() } }));
    } catch (e) {
      DumpAPI.toast(e.message);
    }
  },

  async addTaskEvent(task) {
    const g = DumpStore.getState().google;
    if (g.demo) {
      const date = task.dueDate || DumpUtil.todayISO();
      if (date === DumpUtil.todayISO()) {
        DumpStore.set((s) => ({ google: { ...s.google, events: s.google.events.concat([{ time: 'All day', title: task.text }]) } }));
      }
      DumpAPI.toast('Added to Google Calendar' + (task.dueDate ? ' on ' + DumpUtil.dueLabel(task.dueDate) : ' today') + '.');
      return;
    }
    try {
      const date = task.dueDate || DumpUtil.todayISO();
      const body = {
        summary: task.text,
        description: 'From Dump' + (task.notes ? '\n' + task.notes : ''),
        start: { date },
        end: { date }
      };
      await this.gFetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body)
      });
      DumpAPI.toast('Added to Google Calendar' + (task.dueDate ? ' on ' + DumpUtil.dueLabel(task.dueDate) : ' today') + '.');
      this.fetchTodayEvents();
    } catch (e) {
      DumpAPI.toast(e.message);
    }
  },

  async scanGmail() {
    const data = await this.gFetch('https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=12&q=newer_than:3d%20category:primary');
    const ids = (data.messages || []).map((m) => m.id).slice(0, 12);
    const snippets = [];
    for (const id of ids) {
      const m = await this.gFetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/' + id + '?format=metadata&metadataHeaders=Subject&metadataHeaders=From');
      const subj = ((m.payload && m.payload.headers) || []).find((h) => h.name === 'Subject');
      snippets.push((subj ? subj.value + ' — ' : '') + (m.snippet || ''));
    }
    return snippets;
  }
};
window.GoogleHelper = GoogleHelper;

/* ---------- Suggestions ---------- */
function addSuggestions(source, items) {
  const sugs = items.map((it) => ({
    id: DumpUtil.uid(),
    source,
    text: it.text,
    snippet: it.snippet || '',
    from: it.from || '',
    due: it.due || null,
    priority: it.priority || null,
    dismissed: false
  }));
  if (sugs.length) {
    DumpStore.set((s) => ({ suggestions: s.suggestions.concat(sugs) }));
  }
  return sugs.length;
}

// Demo scans: cards arrive one by one, like a real fetch streaming in
function stageSuggestions(source, items, gapMs = 380) {
  items.forEach((it, i) => {
    setTimeout(() => addSuggestions(source, [it]), i * gapMs);
  });
  return items.length;
}

async function extractActions(rawText, source) {
  let items;
  if (!DumpAPI.aiOn()) {
    await new Promise((r) => setTimeout(r, 800));
    items = (window.DumpDemo ? DumpDemo.sort(rawText) : DumpAPI.mockDump(rawText))
      .map((t) => ({ text: t.text, snippet: '', due: t.dueDate || null, priority: t.priority === 'high' ? 'high' : null }));
  } else {
    const reply = await DumpAPI.runAI(
      'connect',
      { today: DumpUtil.todayISO(), isDoc: false },
      rawText, 1200
    );
    items = DumpAPI.parseJSONReply(reply);
  }
  items = items.filter((i) => i && i.text);
  return stageSuggestions(source, items, 250);
}

function SuggestionCard({ sug }) {
  const [busy, setBusy] = useState(false);
  const srcMeta = {
    gmail: { label: 'Gmail', icon: Icons.mail(13) },
    whatsapp: { label: 'WhatsApp', icon: Icons.chat(13) },
    file: { label: 'File', icon: Icons.file(13) },
    slack: { label: 'Slack', icon: Icons.slack(13) }
  }[sug.source] || { label: sug.source, icon: Icons.inbox(13) };

  const dismiss = () => {
    DumpStore.set((s) => ({ suggestions: s.suggestions.filter((x) => x.id !== sug.id) }));
  };

  const addAsTask = async () => {
    setBusy(true);
    try {
      const item = await DumpAPI.normaliseSuggestion(sug.text);
      if (!item) {
        DumpAPI.toast('That one looks like it\u2019s already on your list.');
        dismiss();
        return;
      }
      const st = DumpStore.getState();
      const cats = st.settings.categories;
      const level = (sug.priority === 'high' || item.priority === 'high') ? 'high' : 'normal';
      const task = {
        id: DumpUtil.uid(),
        text: item.text,
        category: cats.includes(item.category) ? item.category : (cats[cats.length - 1] || 'Personal'),
        level,
        priority: level === 'high',
        notes: sug.snippet ? 'From ' + (sug.from || srcMeta.label) + ': \u201C' + sug.snippet + '\u201D' : '',
        done: false, doneAt: null,
        dueDate: sug.due || item.dueDate || null,
        minutes: (typeof item.minutes === 'number' && item.minutes >= 1) ? Math.round(item.minutes) : null,
        big: !!(item.big),
        status: 'active', waitingOn: null, binnedAt: null,
        bucket: 'next-action',
        steps: null,
        source: sug.source,
        origin: { kind: sug.source, label: sug.from || null, ref: DumpUtil.uid() },
        childId: null,
        blockedBy: null,
        clarifyKind: null,
        clarifyAsk: null,
        createdAt: new Date().toISOString(),
        fresh: true
      };
      DumpStore.set((s) => ({
        tasks: s.tasks.concat([task]),
        suggestions: s.suggestions.filter((x) => x.id !== sug.id)
      }));
      DumpAPI.toast('Added: ' + task.text);
    } catch (e) {
      DumpAPI.toast(e.friendly ? e.message : 'Something went wrong — try again.');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="suggestion-card">
      <div className="sug-head">{srcMeta.icon} {srcMeta.label.toUpperCase()}{sug.from ? <span style={{ fontWeight: 500, letterSpacing: 0, textTransform: 'none' }}> · {sug.from}</span> : null}</div>
      <p className="sug-text">{sug.text}</p>
      {sug.snippet ? <p className="sug-snippet">“{sug.snippet}”</p> : null}
      <div className="sug-actions">
        <button className="btn btn-sm btn-primary" onClick={addAsTask} disabled={busy}>
          {busy ? <span className="spinner"></span> : null}
          {busy ? 'Adding…' : 'Add as task'}
        </button>
        <button className="btn btn-sm btn-ghost" onClick={dismiss} disabled={busy}>Dismiss</button>
      </div>
    </div>
  );
}

/* ---------- Connect cards ---------- */
function ConnectCard({ tint, icon, title, status, blurb, children }) {
  return (
    <div className="connect-card">
      <span className="connect-icon" style={{ background: tint.bg, color: tint.fg }}>{icon}</span>
      <div className="connect-body">
        <h3>{title}{status}</h3>
        <p>{blurb}</p>
        {children}
      </div>
    </div>
  );
}

function ConnectSection() {
  const state = useDumpStore();
  const { google, slack, suggestions } = state;
  const sim = DumpAPI.simConnections();
  const ai = DumpAPI.aiOn();
  const [gmailBusy, setGmailBusy] = useState(false);
  const [waBusy, setWaBusy] = useState(false);
  const [waOpen, setWaOpen] = useState(false);
  const [waText, setWaText] = useState('');
  const [slackBusy, setSlackBusy] = useState(false);
  const [fileBusy, setFileBusy] = useState(false);
  const [dragOver, setDragOver] = useState(false);
  const fileRef = useRef(null);

  const sugsBy = (src) => suggestions.filter((s) => s.source === src && !s.dismissed);
  const connectedStatus = (label) => <span className="connect-status">{label || 'Connected'}</span>;

  /* --- Gmail --- */
  const scanGmail = async () => {
    if (!google.connected) {
      DumpAPI.toast('Connect Google first — the scan needs your inbox.');
      return;
    }
    setGmailBusy(true);
    try {
      let n;
      if (google.demo) {
        await new Promise((r) => setTimeout(r, 1400));
        const items = DumpDemo.gmailMessages
          .filter((m) => m.action)
          .map((m) => ({ text: m.action, snippet: m.snippet, from: m.from, due: m.due || null, priority: m.priority || null }));
        n = stageSuggestions('gmail', items);
        DumpAPI.toast('Scanned ' + DumpDemo.gmailMessages.length + ' recent emails — ' + n + ' need something from you.');
      } else {
        const snippets = await GoogleHelper.scanGmail();
        if (!snippets.length) { DumpAPI.toast('Inbox is quiet — nothing recent to scan.'); return; }
        n = await extractActions(snippets.join('\n---\n'), 'gmail');
        DumpAPI.toast(n ? n + ' suggestion' + (n === 1 ? '' : 's') + ' found' : 'Nothing actionable found.');
      }
    } catch (e) {
      DumpAPI.toast(e.friendly || e.message ? e.message : 'Something went wrong — try again.');
    } finally {
      setGmailBusy(false);
    }
  };

  /* --- WhatsApp --- */
  const scanWhatsApp = async () => {
    setWaBusy(true);
    try {
      if (sim) {
        await new Promise((r) => setTimeout(r, 1300));
        const items = DumpDemo.whatsappMessages
          .filter((m) => m.action)
          .map((m) => ({ text: m.action, snippet: m.text, from: m.from + ' · ' + DumpDemo.whatsappGroup, due: m.due || null, priority: m.priority || null }));
        const n = stageSuggestions('whatsapp', items);
        DumpAPI.toast('Read ' + DumpDemo.whatsappGroup + ' — ' + n + ' things need doing.');
      } else if (waText.trim()) {
        const n = await extractActions(waText, 'whatsapp');
        DumpAPI.toast(n ? n + ' suggestion' + (n === 1 ? '' : 's') + ' found' : 'Nothing actionable in there.');
        setWaText('');
        setWaOpen(false);
      } else {
        DumpAPI.toast('Paste some messages in first.');
      }
    } catch (e) {
      DumpAPI.toast(e.friendly ? e.message : 'Something went wrong — try again.');
    } finally {
      setWaBusy(false);
    }
  };

  /* --- Slack --- */
  const connectSlack = async () => {
    const ok = await AuthSim.open('slack');
    if (!ok) return;
    DumpStore.set((s) => ({ slack: { connected: true, demo: true, workspace: DumpDemo.persona.workspace } }));
    DumpAPI.toast('Slack connected to ' + DumpDemo.persona.workspace + '.');
  };
  const scanSlack = async () => {
    setSlackBusy(true);
    await new Promise((r) => setTimeout(r, 1200));
    const items = DumpDemo.slackMessages
      .filter((m) => m.action)
      .map((m) => ({ text: m.action, snippet: m.text, from: m.from + ' in ' + DumpDemo.slackChannel, due: m.due || null, priority: m.priority || null }));
    const n = stageSuggestions('slack', items);
    DumpAPI.toast('Scanned ' + DumpDemo.slackChannel + ' — ' + n + ' mentions need action.');
    setSlackBusy(false);
  };

  /* --- File upload --- */
  const trySampleFile = async () => {
    setFileBusy(true);
    await new Promise((r) => setTimeout(r, 1100));
    const items = DumpDemo.sampleFile.actions.map((a) => ({ ...a, from: DumpDemo.sampleFile.name }));
    const n = stageSuggestions('file', items);
    DumpAPI.toast('Read ' + DumpDemo.sampleFile.name + ' — ' + n + ' actions found.');
    setFileBusy(false);
  };

  const handleFiles = async (files) => {
    const file = files && files[0];
    if (!file) return;
    setFileBusy(true);
    try {
      const name = file.name.toLowerCase();
      let n = 0;
      if (name.endsWith('.txt') || name.endsWith('.md') || file.type.startsWith('text/')) {
        const text = await file.text();
        n = await extractActions(text.slice(0, 20000), 'file');
        DumpAPI.toast(n ? 'Read ' + file.name + ' — ' + n + ' action' + (n === 1 ? '' : 's') + ' found.' : 'Nothing actionable found in ' + file.name + '.');
      } else if ((name.endsWith('.pdf') || file.type.startsWith('image/')) && ai) {
        const b64 = await new Promise((res, rej) => {
          const r = new FileReader();
          r.onload = () => res(String(r.result).split(',')[1]);
          r.onerror = rej;
          r.readAsDataURL(file);
        });
        const block = file.type.startsWith('image/')
          ? { type: 'image', source: { type: 'base64', media_type: file.type, data: b64 } }
          : { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: b64 } };
        const reply = await DumpAPI.runAI(
          'connect',
          { today: DumpUtil.todayISO(), isDoc: true },
          [block, { type: 'text', text: 'Extract every actionable task from this document.' }],
          1200
        );
        const items = DumpAPI.parseJSONReply(reply).filter((i) => i && i.text);
        n = stageSuggestions('file', items.map((i) => ({ ...i, from: file.name })), 250);
        DumpAPI.toast(n ? 'Read ' + file.name + ' — ' + n + ' actions found.' : 'Nothing actionable found in ' + file.name + '.');
      } else if (name.endsWith('.pdf') || file.type.startsWith('image/')) {
        DumpAPI.toast(sim
          ? 'In demo mode, try the sample newsletter below — or drop a .txt file.'
          : 'Add your API key and turn on AI under Settings to read PDFs and images.');
      } else {
        DumpAPI.toast("Can't read that format yet — try PDF, TXT or an image.");
      }
    } catch (e) {
      DumpAPI.toast(e.friendly ? e.message : "Couldn't read that file — try another format.");
    } finally {
      setFileBusy(false);
      if (fileRef.current) fileRef.current.value = '';
    }
  };

  return (
    <div className="section" data-screen-label="Connect">
      <div className="section-head">
        <h1 className="section-title">Connect</h1>
      </div>
      <p className="section-sub">Let the noisy places feed the board — you just approve.</p>

      <div className="connect-grid">

        <ConnectCard
          tint={{ bg: '#E8F0FE', fg: '#1A73E8' }}
          icon={Icons.calendar(20)}
          title="Google Calendar"
          status={google.connected ? connectedStatus() : null}
          blurb="See today's events in your digest, and push tasks onto your calendar."
        >
          {google.connected && google.email ? (
            <p className="connect-account" style={{ margin: '-6px 0 10px' }}>Connected as {google.email}</p>
          ) : null}
          <div className="connect-actions">
            {google.connected ? (
              <React.Fragment>
                <button className="btn btn-sm" onClick={() => GoogleHelper.fetchTodayEvents().then(() => DumpAPI.toast('Calendar refreshed.'))}>Refresh events</button>
                <button className="btn btn-sm btn-ghost" onClick={() => DumpStore.set({ route: 'today' })}>View in Today</button>
                <button className="btn btn-sm btn-ghost btn-danger-ghost" onClick={() => GoogleHelper.disconnect()}>Disconnect</button>
              </React.Fragment>
            ) : (
              <button className="btn btn-sm" onClick={() => GoogleHelper.connect()}>Connect</button>
            )}
          </div>
        </ConnectCard>

        <ConnectCard
          tint={{ bg: '#FCE8E6', fg: '#D93025' }}
          icon={Icons.mail(20)}
          title="Gmail"
          status={google.connected ? connectedStatus() : null}
          blurb="Scan recent mail for things that need doing — bills, forms, replies."
        >
          <div className="connect-actions">
            {google.connected ? (
              <button className="btn btn-sm" onClick={scanGmail} disabled={gmailBusy}>
                {gmailBusy ? <span className="spinner dark"></span> : null}
                {gmailBusy ? 'Scanning inbox…' : 'Scan inbox'}
              </button>
            ) : (
              <button className="btn btn-sm" onClick={() => GoogleHelper.connect()}>Connect Google</button>
            )}
          </div>
          {sugsBy('gmail').map((s) => <SuggestionCard key={s.id} sug={s} />)}
        </ConnectCard>

        <ConnectCard
          tint={{ bg: '#E6F4EA', fg: '#188038' }}
          icon={Icons.chat(20)}
          title="WhatsApp"
          blurb={sim
            ? 'Reads the school and family chats once a day and pulls out the actual to-dos.'
            : 'Paste the school or family chat — it pulls out the actual to-dos.'}
        >
          <div className="connect-actions">
            {sim ? (
              <button className="btn btn-sm" onClick={scanWhatsApp} disabled={waBusy}>
                {waBusy ? <span className="spinner dark"></span> : null}
                {waBusy ? 'Reading chats…' : 'Scan WhatsApp'}
              </button>
            ) : (
              <button className="btn btn-sm" onClick={() => setWaOpen(!waOpen)}>
                {waOpen ? 'Close' : 'Scan WhatsApp'}
              </button>
            )}
          </div>
          {!sim && waOpen ? (
            <div className="paste-box">
              <textarea
                placeholder="Paste recent messages here…"
                value={waText}
                onChange={(e) => setWaText(e.target.value)}
              ></textarea>
              <div className="connect-actions" style={{ marginTop: 8 }}>
                <button className="btn btn-sm btn-primary" onClick={scanWhatsApp} disabled={waBusy}>
                  {waBusy ? <span className="spinner"></span> : null}
                  {waBusy ? 'Scanning…' : 'Extract tasks'}
                </button>
              </div>
            </div>
          ) : null}
          {sugsBy('whatsapp').map((s) => <SuggestionCard key={s.id} sug={s} />)}
        </ConnectCard>

        <ConnectCard
          tint={{ bg: '#EDE7F6', fg: '#5E35B1' }}
          icon={Icons.slack(20)}
          title="Slack"
          status={sim
            ? (slack.connected ? connectedStatus() : null)
            : <span className="connect-status soon">Coming soon</span>}
          blurb={sim
            ? 'Pull action items out of busy channels — mentions, asks, deadlines.'
            : 'Pull action items out of busy channels. Not wired up in this version.'}
        >
          {slack.connected ? (
            <p className="connect-account" style={{ margin: '-6px 0 10px' }}>Workspace: {slack.workspace}</p>
          ) : null}
          <div className="connect-actions">
            {!sim ? (
              <button className="btn btn-sm" disabled={true}>Connect</button>
            ) : slack.connected ? (
              <React.Fragment>
                <button className="btn btn-sm" onClick={scanSlack} disabled={slackBusy}>
                  {slackBusy ? <span className="spinner dark"></span> : null}
                  {slackBusy ? 'Scanning channels…' : 'Scan ' + DumpDemo.slackChannel}
                </button>
                <button className="btn btn-sm btn-ghost btn-danger-ghost"
                  onClick={() => DumpStore.set({ slack: { connected: false, demo: false, workspace: null } })}>Disconnect</button>
              </React.Fragment>
            ) : (
              <button className="btn btn-sm" onClick={connectSlack}>Connect</button>
            )}
          </div>
          {sugsBy('slack').map((s) => <SuggestionCard key={s.id} sug={s} />)}
        </ConnectCard>

        <ConnectCard
          tint={{ bg: 'rgba(245,166,35,0.14)', fg: '#d98c0a' }}
          icon={Icons.file(20)}
          title="Upload a file"
          blurb="School newsletters, meeting notes, scanned letters — drop them in, get the tasks out."
        >
          <div
            className={'dropzone' + (dragOver ? ' over' : '')}
            onClick={() => fileRef.current && fileRef.current.click()}
            onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
            onDragLeave={() => setDragOver(false)}
            onDrop={(e) => { e.preventDefault(); setDragOver(false); handleFiles(e.dataTransfer.files); }}
          >
            {fileBusy ? 'Reading…' : 'Drop a PDF, TXT or image here — or tap to choose'}
          </div>
          <input ref={fileRef} type="file" accept=".pdf,.txt,.md,.docx,image/*" style={{ display: 'none' }}
            onChange={(e) => handleFiles(e.target.files)} />
          {sim ? (
            <button className="linklike" onClick={trySampleFile} disabled={fileBusy}>
              Try a sample: {DumpDemo.sampleFile.name} →
            </button>
          ) : null}
          {sugsBy('file').map((s) => <SuggestionCard key={s.id} sug={s} />)}
        </ConnectCard>

      </div>
    </div>
  );
}

Object.assign(window, { ConnectSection, SuggestionCard });
