// Sync.jsx — reliable submission status + persistent client-side queue
// -----------------------------------------------------------------------------
// This extends the existing Pow Lee sync pill instead of creating a parallel
// system. It provides:
//   • PLCSync — one app-wide queue/status engine
//   • <OfflineIndicator> — topbar pill; distinguishes attention from normal queue
//   • <SyncHost> — persistent strips, toasts, aria-live announcements, drawer mount
//   • IndexedDB-first queue, with localStorage fallback for older/private browsers
//   • stable client_submission_id / Idempotency-Key for retries
// -----------------------------------------------------------------------------

(function injectSyncStyles() {
  if (document.getElementById('plc-sync-styles')) return;
  const css = `
  .plc-net {
    display: inline-flex; align-items: center; gap: 7px;
    height: 34px; padding: 0 11px; border-radius: var(--radius-pill);
    border: 1px solid var(--border-default); background: var(--color-white);
    font: 600 12.5px/1 var(--font-body); color: var(--color-grey-700);
    cursor: pointer; user-select: none; white-space: nowrap;
    transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
  }
  .plc-net:hover { background: var(--color-grey-50); }
  .plc-net__dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
  .plc-net__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
    font-size: 10.5px; font-weight: 800; color: #fff; margin-left: 1px;
  }
  .plc-net--ok .plc-net__dot { background: var(--color-success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
  .plc-net--off { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.10); color: var(--color-yellow-700); }
  .plc-net--off .plc-net__dot { background: var(--color-warning); }
  .plc-net--sync { border-color: rgba(59,130,246,0.5); background: rgba(59,130,246,0.10); color: #1d4ed8; }
  .plc-net--sync .plc-net__dot { background: var(--color-info); }
  .plc-net--attention { border-color: rgba(239,68,68,0.55); background: rgba(239,68,68,0.10); color: var(--color-danger); }
  .plc-net--attention .plc-net__dot { background: var(--color-danger); }
  .plc-net__count--off { background: var(--color-yellow-700); }
  .plc-net__count--sync { background: var(--color-info); }
  .plc-net__count--attention { background: var(--color-danger); }
  .plc-spin { animation: plc-net-spin 0.9s linear infinite; }
  @keyframes plc-net-spin { to { transform: rotate(360deg); } }
  @media (max-width: 768px) { .plc-net__label { display: none; } }

  .plc-sync-live {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
  .plc-net-pop {
    position: absolute; top: calc(100% + 8px); right: 0; width: 340px;
    background: #fff; border: 1px solid var(--border-default); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); z-index: 90; overflow: hidden; font-family: var(--font-body);
  }
  .plc-net-pop__head { padding: 16px; display: flex; gap: 12px; align-items: flex-start; }
  .plc-net-pop__icon { width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
  .plc-net-foot { padding: 12px 16px; display: flex; gap: 8px; border-top: 1px solid var(--border-default); }
  .plc-net-test { padding: 12px 16px; border-top: 1px solid var(--border-default); background: var(--color-grey-50); }
  .plc-net-test__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 5px 0; }
  .plc-sw { width: 38px; height: 22px; border-radius: 11px; border: none; cursor: pointer; padding: 2px; flex-shrink: 0; display: inline-flex; transition: background var(--duration-fast); }
  .plc-sw__knob { width: 18px; height: 18px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.3); transition: transform var(--duration-fast) var(--ease-out); }

  .plc-strip {
    position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
    z-index: 980; display: flex; align-items: center; gap: 11px;
    padding: 11px 16px; border-radius: var(--radius-pill);
    box-shadow: var(--shadow-lg); font: 600 13px/1.35 var(--font-body);
    max-width: calc(100vw - 32px); animation: plc-strip-in 240ms var(--ease-out);
  }
  .plc-strip--off, .plc-strip--sync { background: var(--color-navy); color: #fff; }
  .plc-strip--attention { background: var(--color-danger); color: #fff; }
  .plc-strip__btn {
    border: none; cursor: pointer; font: 700 12px/1 var(--font-body);
    padding: 7px 12px; border-radius: var(--radius-pill); background: rgba(255,255,255,0.16); color: #fff;
    display: inline-flex; align-items: center; gap: 6px;
  }
  .plc-strip__btn:hover { background: rgba(255,255,255,0.26); }
  @keyframes plc-strip-in { from { transform: translate(-50%, 16px); opacity: 0; } to { transform: translate(-50%, 0); opacity: 1; } }

  .plc-toasts { position: fixed; top: 24px; right: 24px; z-index: 1001; display: flex; flex-direction: column; gap: 10px; max-width: 390px; }
  .plc-toast {
    display: flex; align-items: flex-start; gap: 11px; padding: 13px 15px;
    border-radius: var(--radius-md); box-shadow: var(--shadow-lg); color: #fff;
    font: 500 13.5px/1.45 var(--font-body); animation: plc-toast-in 240ms var(--ease-out);
  }
  .plc-toast__x { margin-left: auto; background: transparent; border: none; color: rgba(255,255,255,0.7); cursor: pointer; padding: 0; flex-shrink: 0; display: inline-flex; }
  .plc-toast--success { background: var(--color-success); }
  .plc-toast--info { background: var(--color-navy); }
  .plc-toast--warning, .plc-toast--error { background: var(--color-danger); }
  @keyframes plc-toast-in { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
  @media (prefers-reduced-motion: reduce) {
    .plc-spin, .plc-strip, .plc-toast { animation: none !important; transition: none !important; }
  }
  @media (max-width: 768px) {
    .plc-toasts { left: 16px; right: 16px; top: 16px; max-width: none; }
    .plc-net-pop { position: fixed; left: 16px; right: 16px; top: 70px; width: auto; }
  }
  `;
  const tag = document.createElement('style');
  tag.id = 'plc-sync-styles';
  tag.textContent = css;
  document.head.appendChild(tag);
})();

const STATUS_CONFIG = {
  draft: { label: 'Draft', group: 'waiting', retryable: false, tone: 'gray' },
  queued: { label: 'Waiting to sync', group: 'waiting', retryable: true, tone: 'warning' },
  sending: { label: 'Sending', group: 'sending', retryable: false, tone: 'info' },
  synchronized: { label: 'Synchronized', group: 'synced', retryable: false, tone: 'success' },
  retry_scheduled: { label: 'Retry scheduled', group: 'waiting', retryable: true, tone: 'warning' },
  failed_temporary: { label: 'Temporary failure', group: 'waiting', retryable: true, tone: 'warning' },
  failed_permanent: { label: 'Requires attention', group: 'attention', retryable: false, tone: 'danger' },
  validation_error: { label: 'Validation error', group: 'attention', retryable: false, tone: 'danger' },
  authentication_required: { label: 'Sign in required', group: 'attention', retryable: false, tone: 'danger' },
  conflict: { label: 'Review latest version', group: 'attention', retryable: false, tone: 'danger' },
  already_processed: { label: 'Already saved', group: 'synced', retryable: false, tone: 'success' },
  cancelled: { label: 'Cancelled', group: 'synced', retryable: false, tone: 'gray' },
};

const plcNowIso = () => new Date().toISOString();
const plcRelTime = (ts) => {
  const t = typeof ts === 'number' ? ts : Date.parse(ts || 0);
  const s = Math.max(0, Math.round((Date.now() - t) / 1000));
  if (s < 60) return 'just now';
  if (s < 3600) return Math.floor(s / 60) + ' min ago';
  if (s < 86400) return Math.floor(s / 3600) + ' hr ago';
  return Math.floor(s / 86400) + ' d ago';
};
const plcUuid = () => {
  if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
  return 'cs_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 12);
};
const plcStableStringify = (value) => {
  if (Array.isArray(value)) return '[' + value.map(plcStableStringify).join(',') + ']';
  if (value && typeof value === 'object') {
    return '{' + Object.keys(value).sort().filter(k => !/attempt|timestamp|last_attempt/i.test(k)).map(k => JSON.stringify(k) + ':' + plcStableStringify(value[k])).join(',') + '}';
  }
  return JSON.stringify(value);
};
const plcHash = (value) => {
  const s = plcStableStringify(value);
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); }
  return (h >>> 0).toString(16);
};

const PLCSync = (() => {
  const DB_NAME = 'plc_sync_queue_v2';
  const STORE = 'submissions';
  const FALLBACK_KEY = 'plc_sync_queue_v2_fallback';
  const HISTORY_KEY = 'plc_sync_history_v2';
  let dbPromise = null;
  let items = [];
  let history = [];
  let simulateOffline = false;
  let simulateNext = null;
  let flushing = false;
  let drawerOpen = false;
  let lockUntil = 0;

  try { history = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]') || []; } catch (e) { history = []; }

  const persistHistory = () => {
    history = history.slice(0, 25);
    try { localStorage.setItem(HISTORY_KEY, JSON.stringify(history)); } catch (e) {}
  };
  const emit = () => {
    window.dispatchEvent(new CustomEvent('plc-sync-change'));
    try { localStorage.setItem('plc_sync_broadcast', String(Date.now())); } catch (e) {}
  };
  const toast = (kind, msg) => window.dispatchEvent(new CustomEvent('plc-sync-toast', { detail: { kind, msg } }));
  const announce = (msg) => window.dispatchEvent(new CustomEvent('plc-sync-announce', { detail: { msg } }));
  const isOnline = () => navigator.onLine && !simulateOffline;

  const openDb = () => {
    if (!('indexedDB' in window)) return Promise.reject(new Error('IndexedDB unavailable'));
    if (dbPromise) return dbPromise;
    dbPromise = new Promise((resolve, reject) => {
      const req = indexedDB.open(DB_NAME, 1);
      req.onupgradeneeded = () => {
        const db = req.result;
        if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE, { keyPath: 'id' });
      };
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error || new Error('IndexedDB open failed'));
    });
    return dbPromise;
  };
  const txStore = async (mode) => {
    const db = await openDb();
    return db.transaction(STORE, mode).objectStore(STORE);
  };
  const loadFallback = () => {
    try { return JSON.parse(localStorage.getItem(FALLBACK_KEY) || '[]') || []; } catch (e) { return []; }
  };
  const saveFallback = (rows) => { try { localStorage.setItem(FALLBACK_KEY, JSON.stringify(rows)); } catch (e) {} };
  const loadAll = async () => {
    try {
      const store = await txStore('readonly');
      items = await new Promise((resolve, reject) => {
        const req = store.getAll();
        req.onsuccess = () => resolve(req.result || []);
        req.onerror = () => reject(req.error);
      });
    } catch (e) {
      items = loadFallback();
    }
    emit();
    return items;
  };
  const saveItem = async (item) => {
    const next = items.filter(x => x.id !== item.id).concat(item).sort((a, b) => String(a.created_at_client).localeCompare(String(b.created_at_client)));
    items = next;
    try {
      const store = await txStore('readwrite');
      await new Promise((resolve, reject) => {
        const req = store.put(item);
        req.onsuccess = resolve;
        req.onerror = () => reject(req.error);
      });
    } catch (e) {
      saveFallback(items);
    }
    emit();
    return item;
  };
  const removeItem = async (id) => {
    items = items.filter(x => x.id !== id);
    try {
      const store = await txStore('readwrite');
      await new Promise((resolve, reject) => {
        const req = store.delete(id);
        req.onsuccess = resolve;
        req.onerror = () => reject(req.error);
      });
    } catch (e) {
      saveFallback(items);
    }
    emit();
  };
  const classify = (err, envelope) => {
    const status = err && (err.status || err.code);
    const code = String((err && err.code) || (envelope && envelope.status) || status || '').toLowerCase();
    if (simulateNext) {
      const s = simulateNext; simulateNext = null;
      if (s === 'auth') return { status: 'authentication_required', code: 'AUTHENTICATION_REQUIRED', message: 'Please sign in again to submit this pending entry.' };
      if (s === 'conflict') return { status: 'conflict', code: 'RECORD_VERSION_CONFLICT', message: 'This record was changed by another user. Review the latest version before continuing.' };
      if (s === 'validation') return { status: 'validation_error', code: 'INVALID_INPUT', message: 'This entry requires correction before it can be submitted.' };
    }
    if (code === 'already_processed' || (envelope && envelope.status === 'already_processed')) {
      return { status: 'already_processed', code: 'ALREADY_PROCESSED', message: 'This entry was already saved. No duplicate was created.' };
    }
    if (status === 401 || code.includes('auth')) return { status: 'authentication_required', code: 'AUTHENTICATION_REQUIRED', message: 'Please sign in again to submit this pending entry.' };
    if (status === 409 || code.includes('conflict')) return { status: 'conflict', code: 'RECORD_VERSION_CONFLICT', message: 'This record was changed by another user. Review the latest version before continuing.' };
    if (status === 422 || code.includes('validation')) return { status: 'validation_error', code: 'INVALID_INPUT', message: (err && err.message) || 'This entry requires correction before it can be submitted.', fields: err && err.fields };
    if ([408, 425, 429, 500, 502, 503, 504].includes(Number(status)) || !status) return { status: 'failed_temporary', code: 'TEMPORARY_FAILURE', message: 'The entry has not been confirmed. It remains safely queued.' };
    return { status: 'failed_permanent', code: String(status || 'REQUEST_FAILED'), message: (err && err.message) || 'This entry requires attention before it can be submitted.' };
  };
  const backoffMs = (retryCount) => {
    const base = [2000, 5000, 15000, 30000][Math.min(retryCount, 3)] || 60000;
    return base + Math.floor(Math.random() * Math.min(base, 5000));
  };
  const addHistory = (item, result) => {
    history.unshift({
      id: item.id, title: item.title, entity_type: item.entity_type, operation_type: item.operation_type,
      client_submission_id: item.client_submission_id, server_record_id: result && result.recordId,
      status: result && result.status || item.current_status, at: plcNowIso(),
    });
    persistHistory();
  };
  const snapshot = () => {
    const attention = items.filter(x => (STATUS_CONFIG[x.current_status] || {}).group === 'attention');
    const sending = items.filter(x => x.current_status === 'sending');
    const waiting = items.filter(x => ['queued', 'retry_scheduled', 'failed_temporary'].includes(x.current_status));
    return {
      online: isOnline(), rawOnline: navigator.onLine, simulateOffline, simulateNext, flushing, drawerOpen,
      pending: waiting.length + sending.length, attention: attention.length, sending: sending.length,
      waiting: waiting.length, items: items.slice(), history: history.slice(), statuses: STATUS_CONFIG,
    };
  };
  const makeItem = (opts) => {
    const payload = opts.payload || {};
    const clientId = opts.clientSubmissionId || payload.client_submission_id || payload.clientSubmissionId || plcUuid();
    const withId = Object.assign({}, payload, { client_submission_id: clientId });
    return {
      id: clientId,
      client_submission_id: clientId,
      entity_type: opts.entityType || opts.entity_type || 'submission',
      operation_type: opts.operationType || opts.operation_type || 'create',
      record_ref: opts.recordRef || opts.record_ref || null,
      title: opts.title || 'Pending submission',
      payload: withId,
      payload_version: opts.payloadVersion || 1,
      payload_hash: plcHash(withId),
      created_at_client: opts.createdAt || plcNowIso(),
      first_attempt_at: null,
      last_attempt_at: null,
      synchronized_at: null,
      retry_count: 0,
      next_retry_at: null,
      current_status: 'queued',
      last_error_code: null,
      message: 'No connection. This entry is safely queued and will be submitted when the connection returns.',
      user_id: opts.userId || null,
      original_record_version: opts.originalRecordVersion || null,
    };
  };
  const enqueue = async (opts) => {
    const item = makeItem(opts || {});
    const existing = items.find(x => x.id === item.id);
    if (existing && existing.payload_hash !== item.payload_hash) {
      const conflict = Object.assign({}, existing, {
        current_status: 'conflict',
        last_error_code: 'CLIENT_IDEMPOTENCY_CONFLICT',
        message: 'This retry has different data from the original submission. Review before continuing.',
      });
      await saveItem(conflict);
      toast('warning', conflict.message);
      return conflict;
    }
    await saveItem(existing || item);
    announce('Submission queued safely on this device.');
    return existing || item;
  };
  const markResult = async (item, result) => {
    const status = result && result.status || 'synchronized';
    const done = ['synchronized', 'already_processed'].includes(status);
    const next = Object.assign({}, item, {
      current_status: done ? status : (STATUS_CONFIG[status] ? status : 'synchronized'),
      synchronized_at: done ? plcNowIso() : item.synchronized_at,
      server_record_id: result && (result.recordId || result.id || result.booking_ref),
      message: result && result.message || (status === 'already_processed' ? 'This entry was already saved. No duplicate was created.' : 'Saved successfully.'),
    });
    if (done) {
      addHistory(next, result);
      await removeItem(item.id);
    } else {
      await saveItem(next);
    }
    return next;
  };
  const reportOutcome = async (meta, errOrResult) => {
    const item = await enqueue(meta || {});
    if (!errOrResult || errOrResult.ok || errOrResult.success || errOrResult.id || errOrResult.booking_ref) {
      const done = await markResult(item, Object.assign({ status: 'synchronized' }, errOrResult || {}));
      toast('success', done.message || 'Saved successfully.');
      return done;
    }
    const c = classify(errOrResult);
    const retryable = c.status === 'failed_temporary';
    const next = Object.assign({}, item, {
      current_status: retryable ? 'retry_scheduled' : c.status,
      last_error_code: c.code,
      message: c.message,
      field_errors: c.fields || errOrResult.fields || null,
      retry_count: item.retry_count || 0,
      next_retry_at: retryable ? new Date(Date.now() + backoffMs(item.retry_count || 0)).toISOString() : null,
    });
    await saveItem(next);
    toast(retryable ? 'warning' : 'error', c.message);
    announce(c.message);
    return next;
  };
  const sendItem = async (item) => {
    if (!isOnline()) return;
    const current = items.find(x => x.id === item.id) || item;
    if ((STATUS_CONFIG[current.current_status] || {}).group === 'attention') return;
    if (current.next_retry_at && Date.parse(current.next_retry_at) > Date.now()) return;
    const sending = Object.assign({}, current, {
      current_status: 'sending',
      first_attempt_at: current.first_attempt_at || plcNowIso(),
      last_attempt_at: plcNowIso(),
    });
    await saveItem(sending);
    try {
      let result;
      if (current.entity_type === 'booking' && window.PLC_API) {
        result = current.operation_type === 'update'
          ? await window.PLC_API.update('bookings', current.record_ref, current.payload, { idempotencyKey: current.client_submission_id })
          : await window.PLC_API.createBooking(current.payload, { idempotencyKey: current.client_submission_id });
      } else {
        throw Object.assign(new Error('No retry handler is available for this submission.'), { status: 400, code: 'NO_RETRY_HANDLER' });
      }
      await markResult(sending, Object.assign({ status: 'synchronized' }, result || {}));
      toast('success', result && result.message || 'Saved successfully.');
      return result || { ok: true };
    } catch (err) {
      const c = classify(err);
      const retryable = c.status === 'failed_temporary';
      const nextRetry = retryable ? new Date(Date.now() + backoffMs((sending.retry_count || 0) + 1)).toISOString() : null;
      await saveItem(Object.assign({}, sending, {
        current_status: retryable ? 'retry_scheduled' : c.status,
        retry_count: (sending.retry_count || 0) + 1,
        next_retry_at: nextRetry,
        last_error_code: c.code,
        message: c.message,
        field_errors: c.fields || err.fields || null,
      }));
      if (!retryable) toast('error', c.message);
      throw err;
    }
  };
  const flush = async (opts) => {
    opts = opts || {};
    if (flushing || !items.length || !isOnline()) return;
    if (Date.now() < lockUntil) return;
    lockUntil = Date.now() + 15000;
    flushing = true; emit();
    try {
      const retryable = items.filter(x => ['queued', 'retry_scheduled', 'failed_temporary'].includes(x.current_status));
      for (const item of retryable) await sendItem(item);
    } finally {
      flushing = false; lockUntil = 0; emit();
    }
  };
  const submitApi = async (opts) => {
    const item = await enqueue(opts || {});
    if (!isOnline()) {
      toast('info', `No connection — “${item.title}” is safely queued and will submit automatically.`);
      return Promise.reject(Object.assign(new Error('Queued while offline'), { status: 0, code: 'QUEUED_OFFLINE', queued: true }));
    }
    let result;
    try {
      result = await sendItem(item);
    } catch (err) {
      const stillAfterError = items.find(x => x.id === item.id);
      return Promise.reject(Object.assign(err, { queued: true, syncItem: stillAfterError || item }));
    }
    const still = items.find(x => x.id === item.id);
    if (still) return Promise.reject(Object.assign(new Error(still.message || 'Submission was not confirmed'), { status: still.last_error_code, code: still.last_error_code, queued: true, syncItem: still }));
    return result || { ok: true, status: 'synchronized', clientSubmissionId: item.client_submission_id };
  };
  const submit = ({ title, message }) => {
    toast(isOnline() ? 'success' : 'info', isOnline() ? (message || `${title} submitted.`) : `No connection — “${title}” was saved locally.`);
    return isOnline();
  };
  const cancel = async (id) => {
    const item = items.find(x => x.id === id);
    if (!item) return;
    addHistory(Object.assign({}, item, { current_status: 'cancelled' }), { status: 'cancelled' });
    await removeItem(id);
    toast('info', 'Pending submission cancelled.');
  };
  const setDrawerOpen = (v) => { drawerOpen = !!v; emit(); };
  const simulate = async (kind) => {
    simulateNext = kind;
    const id = plcUuid();
    await reportOutcome({
      title: 'Simulated ' + kind + ' booking',
      entityType: 'booking',
      operationType: 'create',
      clientSubmissionId: id,
      payload: { client_submission_id: id, simulated: true, kind },
    }, Object.assign(new Error('Simulated ' + kind), { status: kind === 'auth' ? 401 : kind === 'conflict' ? 409 : 422, code: kind }));
  };

  window.addEventListener('online', () => { emit(); toast('info', 'Back online — checking pending submissions…'); flush(); });
  window.addEventListener('offline', () => { emit(); toast('warning', "You're offline — submissions will be saved on this device."); });
  window.addEventListener('focus', () => flush({ silent: true }));
  window.addEventListener('storage', (e) => { if (e.key === 'plc_sync_broadcast') loadAll(); });
  setInterval(() => { if (items.length && isOnline()) flush({ silent: true }); }, 12000);
  loadAll();

  return {
    STATUS_CONFIG, snapshot, loadAll, isOnline, enqueue, submitApi, reportOutcome, flush, submit,
    retry: (id) => id ? sendItem(items.find(x => x.id === id)).then(() => emit()) : flush(),
    retryAll: () => flush(),
    cancel, setDrawerOpen, simulate,
    setSimulateOffline: (v) => { simulateOffline = !!v; emit(); if (!simulateOffline) flush(); },
  };
})();
window.PLCSync = PLCSync;

function useSync() {
  const [s, setS] = React.useState(() => PLCSync.snapshot());
  React.useEffect(() => {
    const h = () => setS(PLCSync.snapshot());
    window.addEventListener('plc-sync-change', h);
    window.addEventListener('online', h);
    window.addEventListener('offline', h);
    return () => {
      window.removeEventListener('plc-sync-change', h);
      window.removeEventListener('online', h);
      window.removeEventListener('offline', h);
    };
  }, []);
  return s;
}

function OfflineIndicator() {
  const s = useSync();
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  let variant = 'ok', label = 'Online', Ico = null, spin = false;
  if (s.attention) { variant = 'attention'; label = 'Needs attention'; Ico = AlertTriangle; }
  else if (s.flushing || s.sending) { variant = 'sync'; label = 'Syncing…'; Ico = RefreshCw; spin = true; }
  else if (!s.online) { variant = 'off'; label = 'Offline'; Ico = Wifi; }
  else if (s.pending) { variant = 'sync'; label = 'Pending'; Ico = RefreshCw; }

  const headTitle = s.attention ? 'Submissions need attention' : (!s.online ? "You're offline" : (s.pending ? 'Pending submissions' : 'Connected'));
  const headBody = s.attention
    ? `${s.attention} item${s.attention > 1 ? 's need' : ' needs'} review. No data has been silently lost.`
    : (!s.online
      ? 'New submissions are saved safely on this device and will retry when the connection returns.'
      : (s.pending ? `${s.pending} item${s.pending > 1 ? 's are' : ' is'} waiting or sending.` : 'All confirmed submissions are synchronized.'));
  const Toggle = ({ on, onClick }) => (
    <button type="button" className="plc-sw" onClick={onClick}
      style={{ background: on ? 'var(--color-navy)' : 'var(--color-grey-300)', justifyContent: 'flex-start' }}
      aria-label="Toggle sync testing option">
      <span className="plc-sw__knob" style={{ transform: on ? 'translateX(16px)' : 'translateX(0)' }}/>
    </button>
  );
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" className={'plc-net plc-net--' + variant}
        onClick={() => setOpen(o => !o)} title="Connection & sync status" aria-label={label + (s.attention ? `, ${s.attention} requiring attention` : s.pending ? `, ${s.pending} pending` : '')}>
        {Ico ? <Ico size={14} className={spin ? 'plc-spin' : ''}/> : <span className="plc-net__dot"/>}
        <span className="plc-net__label">{label}</span>
        {s.attention ? <span className="plc-net__count plc-net__count--attention">{s.attention}</span> : (s.pending ? <span className="plc-net__count plc-net__count--sync">{s.pending}</span> : null)}
      </button>
      {open ? (
        <div className="plc-net-pop">
          <div className="plc-net-pop__head">
            <div className="plc-net-pop__icon" style={{ background: s.attention ? 'rgba(239,68,68,0.12)' : 'rgba(59,130,246,0.12)', color: s.attention ? 'var(--color-danger)' : 'var(--color-info)' }}>
              {s.attention ? <AlertTriangle size={20}/> : (!s.online ? <Wifi size={20}/> : (s.pending ? <RefreshCw size={20} className={s.flushing ? 'plc-spin' : ''}/> : <Check size={20}/>))}
            </div>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--color-navy)' }}>{headTitle}</div>
              <div style={{ fontSize: 12, color: 'var(--color-grey-600)', marginTop: 3, lineHeight: 1.45 }}>{headBody}</div>
            </div>
          </div>
          <div className="plc-net-foot">
            <button type="button" className="btn btn--primary" style={{ flex: 1, height: 38, fontSize: 13 }}
              onClick={() => { setOpen(false); PLCSync.setDrawerOpen(true); }}>
              Pending submissions
            </button>
            <button type="button" className="btn btn--ghost" style={{ height: 38, fontSize: 13 }}
              disabled={!s.online || s.flushing || (!s.pending && !s.attention)}
              onClick={() => PLCSync.retryAll()}>
              Retry all
            </button>
          </div>
          <div className="plc-net-test">
            <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--color-grey-400)', marginBottom: 4 }}>Testing</div>
            <div className="plc-net-test__row"><span style={{ fontSize: 12.5, fontWeight: 600 }}>Simulate offline</span><Toggle on={s.simulateOffline} onClick={() => PLCSync.setSimulateOffline(!s.simulateOffline)}/></div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
              <button type="button" className="btn btn--ghost btn--sm" onClick={() => PLCSync.simulate('validation')}>validation</button>
              <button type="button" className="btn btn--ghost btn--sm" onClick={() => PLCSync.simulate('conflict')}>conflict</button>
              <button type="button" className="btn btn--ghost btn--sm" onClick={() => PLCSync.simulate('auth')}>auth</button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
}

function SubmissionStatus({ item }) {
  const cfg = STATUS_CONFIG[item.current_status] || STATUS_CONFIG.queued;
  const color = cfg.tone === 'danger' ? 'var(--color-danger)' : cfg.tone === 'success' ? 'var(--color-success)' : cfg.tone === 'info' ? 'var(--color-info)' : 'var(--color-warning)';
  return (
    <div style={{ border: '1px solid var(--border-default)', borderRadius: 10, padding: 12, background: '#fff', display: 'grid', gap: 7 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <span style={{ width: 8, height: 8, borderRadius: '50%', background: color, flexShrink: 0 }}/>
        <strong style={{ color: 'var(--color-navy)', minWidth: 0, flex: 1 }}>{item.title}</strong>
        <span style={{ fontSize: 11, fontWeight: 800, color }}>{cfg.label}</span>
      </div>
      <div style={{ fontSize: 12, color: 'var(--color-grey-600)' }}>{item.message || 'Submission is being tracked.'}</div>
      <div style={{ fontSize: 11, color: 'var(--color-grey-500)', fontFamily: 'var(--font-mono)' }}>{item.client_submission_id}</div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        {cfg.retryable || item.current_status === 'authentication_required' ? <button type="button" className="btn btn--ghost btn--sm" onClick={() => PLCSync.retry(item.id)}><RefreshCw size={13}/> Retry</button> : null}
        {item.current_status === 'authentication_required' ? <button type="button" className="btn btn--primary btn--sm" onClick={() => alert('Please sign in again, then press Retry.')}>Sign In</button> : null}
        {item.current_status === 'conflict' ? <button type="button" className="btn btn--ghost btn--sm" onClick={() => alert('Reload the latest booking, compare changes, then submit again if needed.')}>Review Latest Version</button> : null}
        {item.current_status === 'validation_error' ? <button type="button" className="btn btn--ghost btn--sm" onClick={() => alert('Open the original form, correct the highlighted fields, then retry.')}>Edit and Retry</button> : null}
        {cfg.group !== 'synced' ? <button type="button" className="btn btn--ghost btn--sm" onClick={() => { if (confirm('Cancel this pending submission? This only removes the local pending copy; it does not delete any server record.')) PLCSync.cancel(item.id); }}><X size={13}/> Cancel</button> : null}
      </div>
    </div>
  );
}

function SyncHost() {
  const s = useSync();
  const [toasts, setToasts] = React.useState([]);
  const [announceMsg, setAnnounceMsg] = React.useState('');
  React.useEffect(() => {
    const onToast = (e) => {
      const id = Date.now() + Math.random();
      const t = { id, kind: e.detail.kind, msg: e.detail.msg };
      setToasts(list => [...list, t]);
      setTimeout(() => setToasts(list => list.filter(x => x.id !== id)), e.detail.kind === 'warning' || e.detail.kind === 'error' ? 6000 : 3600);
    };
    const onAnnounce = (e) => setAnnounceMsg(e.detail.msg || '');
    window.addEventListener('plc-sync-toast', onToast);
    window.addEventListener('plc-sync-announce', onAnnounce);
    return () => {
      window.removeEventListener('plc-sync-toast', onToast);
      window.removeEventListener('plc-sync-announce', onAnnounce);
    };
  }, []);
  const dismiss = (id) => setToasts(list => list.filter(x => x.id !== id));

  let strip = null;
  if (s.attention) {
    strip = <div className="plc-strip plc-strip--attention" role="alert"><AlertTriangle size={17}/><span>{s.attention} submission{s.attention > 1 ? 's need' : ' needs'} attention.</span><button type="button" className="plc-strip__btn" onClick={() => PLCSync.setDrawerOpen(true)}>Review</button></div>;
  } else if (!s.online) {
    strip = <div className="plc-strip plc-strip--off" role="status"><Wifi size={17}/><span>{s.pending ? `Offline — ${s.pending} submission${s.pending > 1 ? 's' : ''} safely queued.` : "You're offline — new submissions will be queued."}</span></div>;
  } else if (s.pending) {
    strip = <div className="plc-strip plc-strip--sync" role="status"><RefreshCw size={17} className={s.flushing ? 'plc-spin' : ''}/><span>{s.flushing ? `Syncing ${s.pending} submission${s.pending > 1 ? 's' : ''}…` : `${s.pending} submission${s.pending > 1 ? 's' : ''} waiting to sync.`}</span><button type="button" className="plc-strip__btn" onClick={() => PLCSync.retryAll()}>Retry all</button></div>;
  }

  return (
    <React.Fragment>
      <div className="plc-sync-live" aria-live="polite">{announceMsg}</div>
      {strip}
      {toasts.length ? (
        <div className="plc-toasts">
          {toasts.map(t => (
            <div key={t.id} className={'plc-toast plc-toast--' + t.kind}>
              {t.kind === 'success' ? <Check size={17} style={{ flexShrink: 0, marginTop: 1 }}/> : t.kind === 'info' ? <Wifi size={17} style={{ flexShrink: 0, marginTop: 1 }}/> : <AlertTriangle size={17} style={{ flexShrink: 0, marginTop: 1 }}/>}
              <span>{t.msg}</span>
              <button type="button" className="plc-toast__x" onClick={() => dismiss(t.id)} aria-label="Dismiss sync message"><X size={15}/></button>
            </div>
          ))}
        </div>
      ) : null}
      {window.PendingSubmissionsDrawer ? <window.PendingSubmissionsDrawer/> : null}
    </React.Fragment>
  );
}

Object.assign(window, { STATUS_CONFIG, PLCSync, useSync, OfflineIndicator, SyncHost, SubmissionStatus, plcRelTime });
