// Brook Consultancy — Sync UI (banner + settings modal + conflict modal)
//
// Sits between the app header and the GDPR banner. Shows connection status,
// last-synced time, sync errors, and offers a Sync Now button + auto-sync toggle.
//
// Reads/writes Drive settings via window.loadDriveSettings / window.saveDriveSettings,
// and drives window.syncAll() to merge state.

// NOTE: not wrapped in an IIFE — Babel-standalone JSX inside an IIFE has surprising
// scope behaviour and we want all helpers and components at module top level so
// every JSX node compiles to a React.createElement call against the top-level binding.

const SYNC_ROLE_POLL_MS = {
  consultant: 30_000,
  operator:   10_000,
  tv:         20_000,
  office:     60_000   // pass-1 default
};

// ---------------------------------------------------------------
// Apps Script source — copy-pasted from the Settings modal into a fresh
// Google Apps Script project. Deployed as a Web App with "Anyone" access so
// the public booking page can POST without auth. Writes booking JSONs into
// the same Shared Drive folder the operator queue pulls from.
// ---------------------------------------------------------------
const APPS_SCRIPT_SOURCE = `// Brook Consultancy — Booking endpoint (Apps Script Web App)
//
// Deploy: New deployment → Web app
//   Execute as: Me (the admin account)
//   Who has access: Anyone (yes, even anonymous — bookings are public submissions)
//
// After deploying, copy the Web App URL into the Brook app's Settings → Bookings tab.

const SHARED_DRIVE_ID = 'PASTE_YOUR_SHARED_DRIVE_ID_HERE'; // same as on the Drive tab
const MAIN_FOLDER     = 'Brook Consultancy - Event Sessions';

function doPost(e) {
  try {
    const payload = JSON.parse(e.postData.contents);
    if (payload.action === 'create_booking') {
      return _createBooking(payload.record);
    }
    return _json({ error: 'Unknown action: ' + payload.action }, 400);
  } catch (err) {
    return _json({ error: String(err && err.message || err) }, 500);
  }
}

function doGet(e) {
  try {
    const action = (e.parameter && e.parameter.action) || '';
    if (action === 'list_bookings') {
      return _listBookings(e.parameter.date || '');
    }
    return _json({ error: 'Unknown action: ' + action }, 400);
  } catch (err) {
    return _json({ error: String(err && err.message || err) }, 500);
  }
}

function _createBooking(record) {
  if (!record || record.kind !== 'booking' || !record._id) {
    return _json({ error: 'Invalid booking record' }, 400);
  }
  // Server-side timestamps to keep slot validation honest.
  record.updatedAt   = new Date().toISOString();
  record._started_at = record._started_at || record.updatedAt;
  record.booking     = record.booking || {};
  record.booking.submitted_at = new Date().toISOString();

  const folder   = _findOrCreateFolder_(MAIN_FOLDER, SHARED_DRIVE_ID);
  const filename = 'booking-' + record._id + '.json';
  const content  = JSON.stringify(record, null, 2);

  // Drive.Files.insert with supportsAllDrives — works on Shared Drives.
  const metadata = {
    name: filename,
    mimeType: 'application/json',
    parents: [{ id: folder }],
    appProperties: {
      recordId: record._id,
      kind: 'booking',
      status: record.status || 'booked',
      orgName: record.booking.business_name || '',
      slotIso: record.booking.slot_iso || '',
      updatedAt: record.updatedAt
    }
  };
  const blob = Utilities.newBlob(content, 'application/json', filename);
  const file = Drive.Files.insert(metadata, blob, { supportsAllDrives: true });
  return _json({ ok: true, id: file.id, recordId: record._id });
}

function _listBookings(dateStr) {
  const folder = _findOrCreateFolder_(MAIN_FOLDER, SHARED_DRIVE_ID);
  const q = "'" + folder + "' in parents and trashed = false and name contains 'booking-'";
  const res = Drive.Files.list({
    q: q,
    pageSize: 1000,
    fields: 'files(id,name,modifiedTime,appProperties)',
    supportsAllDrives: true,
    includeItemsFromAllDrives: true,
    corpora: 'drive',
    driveId: SHARED_DRIVE_ID
  });
  const bookings = (res.files || [])
    .filter(f => !dateStr || (f.appProperties && f.appProperties.slotIso && f.appProperties.slotIso.indexOf(dateStr) === 0))
    .map(f => ({
      _id: (f.appProperties && f.appProperties.recordId) || f.name.replace('booking-','').replace('.json',''),
      kind: 'booking',
      status: (f.appProperties && f.appProperties.status) || 'booked',
      updatedAt: f.modifiedTime,
      booking: {
        slot_iso: (f.appProperties && f.appProperties.slotIso) || '',
        business_name: (f.appProperties && f.appProperties.orgName) || ''
      }
    }));
  return _json({ bookings: bookings });
}

function _findOrCreateFolder_(name, driveId) {
  const q = "mimeType = 'application/vnd.google-apps.folder' and name = '" + name.replace(/'/g, "\\\\'") + "' and trashed = false";
  const res = Drive.Files.list({
    q: q, fields: 'files(id,name)',
    supportsAllDrives: true, includeItemsFromAllDrives: true,
    corpora: 'drive', driveId: driveId
  });
  if (res.files && res.files.length) return res.files[0].id;
  const folder = Drive.Files.insert({
    name: name,
    mimeType: 'application/vnd.google-apps.folder',
    parents: [{ id: driveId }]
  }, null, { supportsAllDrives: true });
  return folder.id;
}

function _json(obj, status) {
  return ContentService.createTextOutput(JSON.stringify(obj))
    .setMimeType(ContentService.MimeType.JSON);
}
// NOTE: Apps Script's HtmlService/ContentService can't actually set non-200
// status codes. The 'status' arg above is ignored at runtime; clients should
// detect errors via the 'error' key in the JSON body.
`;

  function relTime(iso) {
    if (!iso) return null;
    const ms = Date.now() - Date.parse(iso);
    if (Number.isNaN(ms) || ms < 0) return 'just now';
    const s = Math.floor(ms / 1000);
    if (s < 60) return `${s}s ago`;
    const m = Math.floor(s / 60);
    if (m < 60) return `${m} min ago`;
    const h = Math.floor(m / 60);
    return `${h}h ago`;
  }

  // ---------------------------------------------------------------
  // SyncBanner — top of main area, between header and GDPR
  // ---------------------------------------------------------------
  function SyncBanner({ store, setStore, role = 'office' }) {
    const [settings, setSettings] = React.useState(() => window.loadDriveSettings());
    const [connected, setConnected] = React.useState(() => window.hasValidToken());
    const [syncing, setSyncing] = React.useState(false);
    const [autoSync, setAutoSync] = React.useState(true);
    const [showSettings, setShowSettings] = React.useState(false);
    const [conflict, setConflict] = React.useState(null);
    const [lastError, setLastError] = React.useState(null);
    const [, forceTick] = React.useState(0);

    const isConfigured = !!(settings.clientId && settings.sharedDriveId);
    const lastSyncedAt = store?._lastSyncedAt || null;
    const syncErrors = store?._syncErrors || [];

    // Re-render every 15s so the "Synced 12s ago" stamp stays fresh.
    React.useEffect(() => {
      const id = setInterval(() => forceTick(t => t + 1), 15_000);
      return () => clearInterval(id);
    }, []);

    // Whenever connected state changes (token issued/revoked elsewhere), pick it up.
    React.useEffect(() => {
      const check = () => setConnected(window.hasValidToken());
      const id = setInterval(check, 5_000);
      return () => clearInterval(id);
    }, []);

    // Auto-sync polling
    React.useEffect(() => {
      if (!autoSync || !isConfigured || !connected) return;
      const interval = ROLE_POLL_MS[role] || ROLE_POLL_MS.office;
      const tick = () => {
        if (document.hidden) return;
        runSync({ silent: true });
      };
      const id = setInterval(tick, interval);
      return () => clearInterval(id);
    }, [autoSync, isConfigured, connected, role, store]);

    const runSync = React.useCallback(async ({ silent = false } = {}) => {
      if (!isConfigured) {
        if (!silent) setShowSettings(true);
        return;
      }
      if (syncing) return;
      setSyncing(true);
      setLastError(null);
      try {
        const result = await window.syncAll(store, {
          protectLocalIfEditedWithinMs: 30_000,
          applyRecord: (recordId, rec) => {
            // Functional update — never spread captured state. Replace the record by id.
            setStore(prev => ({
              ...prev,
              records: { ...prev.records, [recordId]: rec }
            }));
          },
          onConflict: (recordId, local, remote) => {
            setConflict({ recordId, local, remote });
          }
        });
        setStore(prev => ({
          ...prev,
          fileIdByRecordId: { ...prev.fileIdByRecordId, ...result.fileIdByRecordId },
          _lastSyncedAt: new Date().toISOString(),
          _syncErrors: result.errors || []
        }));
        setConnected(window.hasValidToken());
      } catch (e) {
        console.warn('Sync failed:', e);
        if (!silent) setLastError(e.message || String(e));
        setStore(prev => ({
          ...prev,
          _syncErrors: [{ recordId: '*', message: e.message || String(e), at: new Date().toISOString() }]
        }));
      } finally {
        setSyncing(false);
      }
    }, [isConfigured, syncing, store, setStore]);

    const connect = async () => {
      setLastError(null);
      try {
        await window.ensureToken(settings.clientId);
        setConnected(true);
        // Kick off an immediate sync once connected.
        runSync({ silent: false });
      } catch (e) {
        setLastError(e.message || String(e));
      }
    };

    const signOut = () => {
      window.disconnect();
      setConnected(false);
    };

    const onSettingsSaved = (next) => {
      setSettings(next);
      setShowSettings(false);
      // If newly configured, prompt to connect.
      setConnected(window.hasValidToken());
    };

    // --- Status visuals
    let statusDot, statusLabel;
    if (!isConfigured) {
      statusDot = { color: '#8a93a4', glow: false };
      statusLabel = 'Sync not set up';
    } else if (!connected) {
      statusDot = { color: '#d97706', glow: false };
      statusLabel = 'Not connected';
    } else {
      statusDot = { color: '#15803d', glow: true };
      statusLabel = 'Connected';
    }

    return (
      <>
        <div className="sync-banner">
          <div className="sync-left">
            <span className="sync-dot" style={{ background: statusDot.color, boxShadow: statusDot.glow ? `0 0 0 0 ${statusDot.color}66` : 'none' }} />
            <span className="sync-status">{statusLabel}</span>
            {connected && lastSyncedAt && (
              <span className="sync-time">· Synced {relTime(lastSyncedAt)}</span>
            )}
            {syncing && <span className="sync-spinner" aria-hidden>●</span>}
            {syncErrors.length > 0 && (
              <span className="sync-error" title={syncErrors.map(e => `${e.recordId}: ${e.message}`).join('\n')}>
                · {syncErrors.length} sync error{syncErrors.length === 1 ? '' : 's'}
              </span>
            )}
            {lastError && (
              <span className="sync-error">· {lastError}</span>
            )}
          </div>

          <div className="sync-actions">
            {isConfigured && !connected && (
              <button className="btn btn-amber sync-btn" onClick={connect}>
                Connect to Google Drive
              </button>
            )}
            {isConfigured && connected && (
              <>
                <label className="sync-auto" title="Automatically sync in the background">
                  <input type="checkbox" checked={autoSync} onChange={e => setAutoSync(e.target.checked)} />
                  <span>Auto</span>
                </label>
                <button className="btn sync-btn" onClick={() => runSync({ silent: false })} disabled={syncing}>
                  {syncing ? 'Syncing…' : 'Sync now'}
                </button>
                <button className="btn sync-btn-ghost" onClick={signOut} title="Sign out of Google">
                  Sign out
                </button>
              </>
            )}
            <button className="btn sync-btn-ghost" onClick={() => setShowSettings(true)}>
              {isConfigured ? 'Settings' : 'Set up'}
            </button>
          </div>
        </div>

        {showSettings && (
          <SettingsModal
            initial={settings}
            onClose={() => setShowSettings(false)}
            onSaved={onSettingsSaved}
            onSignOut={signOut}
            connected={connected}
          />
        )}

        {conflict && (
          <ConflictModal
            conflict={conflict}
            onKeepLocal={() => setConflict(null)}
            onTakeRemote={() => {
              setStore(prev => ({
                ...prev,
                records: { ...prev.records, [conflict.recordId]: conflict.remote }
              }));
              setConflict(null);
            }}
          />
        )}
      </>
    );
  }

  // ---------------------------------------------------------------
  // Settings modal
  // ---------------------------------------------------------------
  const CLIENT_ID_RE = /^[0-9]+-[a-z0-9]+\.apps\.googleusercontent\.com$/i;

  function SettingsModal({ initial, onClose, onSaved, onSignOut, connected }) {
    const [tab, setTab] = React.useState('drive');
    const [clientId, setClientId] = React.useState(initial.clientId || '');
    const [sharedDriveId, setSharedDriveId] = React.useState(initial.sharedDriveId || '');
    const [appsScriptUrl, setAppsScriptUrl] = React.useState(initial.appsScriptUrl || '');
    const [bookingDate, setBookingDate] = React.useState(initial.bookingDate || new Date().toISOString().slice(0, 10));
    const [bookingStart, setBookingStart] = React.useState(initial.bookingStart || '09:30');
    const [bookingEnd, setBookingEnd] = React.useState(initial.bookingEnd || '17:00');
    const [bookingTables, setBookingTables] = React.useState(initial.bookingTables || 3);
    const [showGuide, setShowGuide] = React.useState(false);
    const [showAppsScriptCode, setShowAppsScriptCode] = React.useState(false);
    const [copied, setCopied] = React.useState(false);
    const [scriptCopied, setScriptCopied] = React.useState(false);

    const clientIdTrim = clientId.trim();
    const driveIdTrim = sharedDriveId.trim();
    const appsScriptUrlTrim = appsScriptUrl.trim();
    const clientIdValid = CLIENT_ID_RE.test(clientIdTrim);
    const driveIdValid = driveIdTrim.length >= 20 && driveIdTrim.length <= 80 && /^[A-Za-z0-9_-]+$/.test(driveIdTrim);
    const appsScriptUrlValid = !appsScriptUrlTrim || /^https:\/\/script\.google\.com\/.+\/exec(\?.*)?$/.test(appsScriptUrlTrim);
    const origin = window.location.origin;

    const save = () => {
      const next = window.saveDriveSettings({
        clientId: clientIdTrim,
        sharedDriveId: driveIdTrim,
        appsScriptUrl: appsScriptUrlTrim,
        bookingDate,
        bookingStart,
        bookingEnd,
        bookingTables: Number(bookingTables) || 3
      });
      onSaved(next);
    };

    const copyAppsScript = async () => {
      try { await navigator.clipboard.writeText(APPS_SCRIPT_SOURCE); }
      catch { try { window.prompt('Copy the Apps Script source:', APPS_SCRIPT_SOURCE); } catch {} }
      setScriptCopied(true);
      setTimeout(() => setScriptCopied(false), 2200);
    };

    const remove = () => {
      if (!confirm('Remove sync settings? This will sign you out and clear your saved OAuth Client ID and Shared Drive ID from this device.')) return;
      window.saveDriveSettings({ clientId: '', sharedDriveId: '' });
      window.disconnect();
      onSaved({ clientId: '', sharedDriveId: '' });
    };

    const copyOrigin = async () => {
      try { await navigator.clipboard.writeText(origin); }
      catch { try { window.prompt('Copy this origin:', origin); } catch {} }
      setCopied(true);
      setTimeout(() => setCopied(false), 2200);
    };

    return (
      <div className="modal-overlay" onClick={onClose}>
        <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 760 }}>
          <div className="modal-header">
            <div>
              <h2>Sync &amp; bookings settings</h2>
              <div className="modal-sub">Drive sync for the consultation queue · Apps Script for the public booking endpoint</div>
            </div>
            <button className="close-btn" onClick={onClose}>×</button>
          </div>

          <div style={{ display: 'flex', borderBottom: '1px solid var(--border)', padding: '0 24px', background: 'var(--bg-soft)' }}>
            <button className={`settings-tab${tab === 'drive' ? ' active' : ''}`} onClick={() => setTab('drive')}>1. Google Drive sync</button>
            <button className={`settings-tab${tab === 'bookings' ? ' active' : ''}`} onClick={() => setTab('bookings')}>2. Bookings & slots</button>
          </div>

          <div className="modal-body">
            {tab === 'drive' && <>
            <div className="autocomplete-intro" style={{ marginBottom: 18 }}>
              v2 syncs consultations between consultant tablets, the operator desktop, and TV displays via a shared folder on the Brook Google Workspace. Each device needs the same <strong>OAuth Client ID</strong> + <strong>Shared Drive ID</strong>. The Workspace admin sets up the OAuth client once; every device pastes the same values here.
            </div>

            <div className="field">
              <label className="field-label">OAuth Client ID</label>
              <input
                className="input"
                value={clientId}
                onChange={e => setClientId(e.target.value)}
                placeholder="123456789-abcdefg.apps.googleusercontent.com"
                spellCheck={false}
                autoCorrect="off"
                autoCapitalize="off"
                style={clientIdTrim && !clientIdValid ? { borderColor: 'var(--danger)' } : (clientIdValid ? { borderColor: 'var(--success)' } : {})}
              />
              {clientIdTrim && !clientIdValid && (
                <div style={{ fontSize: 12, color: 'var(--danger)', marginTop: 4 }}>
                  Doesn't look right — should end in <code>.apps.googleusercontent.com</code>. Check for stray whitespace.
                </div>
              )}
              {clientIdValid && (
                <div style={{ fontSize: 12, color: 'var(--success)', marginTop: 4 }}>✓ Valid format</div>
              )}
            </div>

            <div className="field">
              <label className="field-label">Shared Drive ID</label>
              <input
                className="input"
                value={sharedDriveId}
                onChange={e => setSharedDriveId(e.target.value)}
                placeholder="0A1B2C3D4E5F6G7H8I9J"
                spellCheck={false}
                autoCorrect="off"
                autoCapitalize="off"
                style={driveIdTrim && !driveIdValid ? { borderColor: 'var(--danger)' } : (driveIdValid ? { borderColor: 'var(--success)' } : {})}
              />
              <div style={{ fontSize: 12, color: 'var(--text-soft)', marginTop: 4 }}>
                Open the Shared Drive in your browser; the ID is the part of the URL after <code>/drive/folders/</code>.
              </div>
            </div>

            <div style={{ background: 'var(--brook-amber-bg)', border: '1px solid #e8d8a8', padding: '12px 14px', borderRadius: 6, marginTop: 10 }}>
              <div style={{ fontSize: 12, color: '#6e5314', marginBottom: 6 }}>
                <strong>This device's origin</strong> — add this to the OAuth client's <em>Authorised JavaScript origins</em> in Google Cloud Console:
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <code style={{ flex: 1, padding: '6px 10px', background: 'white', border: '1px solid #e8d8a8', borderRadius: 4, fontSize: 12, overflow: 'auto', whiteSpace: 'nowrap' }}>{origin}</code>
                <button className="btn" style={{ padding: '6px 12px', fontSize: 12 }} onClick={copyOrigin}>
                  {copied ? '✓ Copied' : 'Copy'}
                </button>
              </div>
            </div>

            <button
              className="sync-guide-toggle"
              onClick={() => setShowGuide(s => !s)}
            >
              {showGuide ? '▼' : '▶'} First-time setup guide (for the Workspace admin)
            </button>
            {showGuide && (
              <ol className="sync-guide">
                <li>Go to <a href="https://console.cloud.google.com/projectcreate" target="_blank" rel="noreferrer">console.cloud.google.com/projectcreate</a> and create a new project (or pick an existing one). Use a brookconsultancy.co.uk admin account.</li>
                <li>Enable the Drive API at <a href="https://console.cloud.google.com/apis/library/drive.googleapis.com" target="_blank" rel="noreferrer">console.cloud.google.com/apis/library/drive.googleapis.com</a>.</li>
                <li>Configure the OAuth consent screen — <strong>User type: Internal</strong>. (This is what makes the sensitive <code>drive</code> scope work without Google verification, because Brook is a Google Workspace.)</li>
                <li>Create an OAuth Client ID — type: <strong>Web application</strong>. Paste each device's origin (shown above) into <em>Authorised JavaScript origins</em>. No redirect URIs needed for this flow.</li>
                <li>Copy the generated Client ID into the field above on every device that will use the app.</li>
                <li>Grab the Shared Drive ID from the Drive URL (e.g. <code>https://drive.google.com/drive/folders/<strong>0A1B...</strong></code>) and paste it above too.</li>
                <li>Click <strong>Save & connect</strong>. The first sign-in will show Google's consent screen; subsequent reloads can re-auth with one click.</li>
              </ol>
            )}
            </>}

            {tab === 'bookings' && <>
            <div className="autocomplete-intro" style={{ marginBottom: 18 }}>
              The booking page (<code>?role=book</code>) is the URL the QR code points at. A prospect scanning it has no Google account, so bookings flow to the Shared Drive via a Google Apps Script Web App that's deployed once by the Workspace admin. Configure the URL + slot schedule below.
            </div>

            <div className="field">
              <label className="field-label">Apps Script Web App URL</label>
              <input
                className="input"
                value={appsScriptUrl}
                onChange={e => setAppsScriptUrl(e.target.value)}
                placeholder="https://script.google.com/macros/s/AKfy.../exec"
                spellCheck={false}
                autoCorrect="off"
                autoCapitalize="off"
                style={appsScriptUrlTrim && !appsScriptUrlValid ? { borderColor: 'var(--danger)' } : (appsScriptUrlValid && appsScriptUrlTrim ? { borderColor: 'var(--success)' } : {})}
              />
              {appsScriptUrlTrim && !appsScriptUrlValid && (
                <div style={{ fontSize: 12, color: 'var(--danger)', marginTop: 4 }}>
                  Should look like <code>https://script.google.com/.../exec</code>. Make sure you've copied the deployed Web App URL, not the editor URL.
                </div>
              )}
              {!appsScriptUrlTrim && (
                <div style={{ fontSize: 12, color: 'var(--text-soft)', marginTop: 4 }}>
                  Without this, the booking page can still render but submissions will fail. See the deployment guide below.
                </div>
              )}
            </div>

            <h3 style={{ fontSize: 14, marginTop: 18, marginBottom: 8, fontFamily: 'var(--font-sans)' }}>Event slot schedule</h3>
            <div className="booking-grid">
              <div className="field">
                <label className="field-label">Event date</label>
                <input className="input" type="date" value={bookingDate} onChange={e => setBookingDate(e.target.value)} />
              </div>
              <div className="field">
                <label className="field-label">Tables (concurrent slots)</label>
                <input className="input" type="number" min={1} max={12} value={bookingTables} onChange={e => setBookingTables(e.target.value)} />
              </div>
              <div className="field">
                <label className="field-label">Doors open</label>
                <input className="input" type="time" value={bookingStart} onChange={e => setBookingStart(e.target.value)} />
              </div>
              <div className="field">
                <label className="field-label">Doors close</label>
                <input className="input" type="time" value={bookingEnd} onChange={e => setBookingEnd(e.target.value)} />
              </div>
            </div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>
              Slot length is fixed at 30 minutes. With {bookingTables || 3} tables, that's up to {bookingTables || 3} concurrent prospects per half-hour.
            </div>

            <button
              className="sync-guide-toggle"
              onClick={() => setShowAppsScriptCode(s => !s)}
            >
              {showAppsScriptCode ? '▼' : '▶'} Apps Script deployment — copy-paste source &amp; steps
            </button>
            {showAppsScriptCode && (
              <>
                <ol className="sync-guide">
                  <li>Open <a href="https://script.google.com/" target="_blank" rel="noreferrer">script.google.com</a> with a brookconsultancy.co.uk admin account and click <strong>New project</strong>.</li>
                  <li>Replace the default <code>Code.gs</code> contents with the code below.</li>
                  <li>At the top of the script, set <code>SHARED_DRIVE_ID</code> to the same Shared Drive ID configured on the Drive tab.</li>
                  <li>Click <strong>Deploy → New deployment → Web app</strong>. Set <em>Execute as: Me (admin)</em>, <em>Who has access: Anyone</em>. (Apps Script will warn that this is publicly accessible — that's correct; bookings are public form submissions.)</li>
                  <li>Authorise the script (it needs Drive access on behalf of the admin). Copy the <strong>Web App URL</strong> and paste it into the field above.</li>
                  <li>If you change the script later, re-deploy as a <em>New version</em> and update the URL here.</li>
                </ol>
                <div style={{ position: 'relative', marginTop: 12 }}>
                  <pre className="apps-script-code">{APPS_SCRIPT_SOURCE}</pre>
                  <button
                    className="btn"
                    style={{ position: 'absolute', top: 8, right: 8, padding: '4px 12px', fontSize: 12 }}
                    onClick={copyAppsScript}
                  >{scriptCopied ? '✓ Copied' : 'Copy code'}</button>
                </div>
              </>
            )}
            </>}
          </div>

          <div className="modal-footer">
            <div className="left">
              {connected ? <span style={{ color: 'var(--success)', fontWeight: 600 }}>● Connected</span> : 'Not connected'}
            </div>
            <div className="actions">
              {(initial.clientId || initial.sharedDriveId) && (
                <button className="btn" style={{ color: 'var(--danger)', borderColor: '#e3b9b3' }} onClick={remove}>Remove settings</button>
              )}
              <button className="btn" onClick={onClose}>Cancel</button>
              <button className="btn btn-primary" onClick={save} disabled={!clientIdValid || !driveIdValid}>
                Save{!connected ? ' & connect' : ''}
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ---------------------------------------------------------------
  // Conflict modal
  // ---------------------------------------------------------------
  function ConflictModal({ conflict, onKeepLocal, onTakeRemote }) {
    const { recordId, local, remote } = conflict;
    const localOrg = local?.fields?.org_name || '(unnamed)';
    const remoteOrg = remote?.fields?.org_name || '(unnamed)';
    const localUpdated = local?.updatedAt ? new Date(local.updatedAt).toLocaleString() : '—';
    const remoteUpdated = remote?.updatedAt ? new Date(remote.updatedAt).toLocaleString() : '—';

    return (
      <div className="modal-overlay">
        <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 620 }}>
          <div className="modal-header" style={{ background: 'var(--warning)' }}>
            <div>
              <h2 style={{ color: 'white' }}>Sync conflict</h2>
              <div className="modal-sub" style={{ color: 'rgba(255,255,255,0.85)' }}>
                Another device updated this consultation while you were editing.
              </div>
            </div>
          </div>
          <div className="modal-body">
            <p style={{ fontSize: 14, color: 'var(--text)' }}>
              The remote version of <strong>{remoteOrg}</strong> is newer than your local version, but you have unsaved edits from the last 30 seconds. Choose which version to keep.
            </p>
            <table className="data-table" style={{ marginTop: 16 }}>
              <thead>
                <tr><th></th><th>Your version</th><th>Remote version</th></tr>
              </thead>
              <tbody>
                <tr><th>Organisation</th><td>{localOrg}</td><td>{remoteOrg}</td></tr>
                <tr><th>Last updated</th><td>{localUpdated}</td><td>{remoteUpdated}</td></tr>
                <tr><th>Status</th><td>{local?.status || '—'}</td><td>{remote?.status || '—'}</td></tr>
                <tr><th>Claimed by</th><td>{local?.claimedBy || '—'}</td><td>{remote?.claimedBy || '—'}</td></tr>
              </tbody>
            </table>
            <div style={{ marginTop: 14, fontSize: 12.5, color: 'var(--text-soft)' }}>
              <strong>Record ID:</strong> {recordId}
            </div>
          </div>
          <div className="modal-footer">
            <div className="left">Last-write-wins is paused until you choose.</div>
            <div className="actions">
              <button className="btn" onClick={onKeepLocal}>Keep editing (discard remote)</button>
              <button className="btn btn-primary" onClick={onTakeRemote}>Take remote version</button>
            </div>
          </div>
        </div>
      </div>
    );
  }

Object.assign(window, { SyncBanner, SettingsModal, ConflictModal });
