const SwalTZ = Swal.mixin({
  buttonsStyling: false,
  backdrop: 'rgba(0, 0, 0, 0)',
  customClass: {
    popup: 'tz-popup',
    title: 'tz-title',
    htmlContainer: 'tz-html',
    confirmButton: 'tz-btn tz-btn-primary',
    cancelButton: 'tz-btn',
    denyButton: 'tz-btn tz-btn-ghost',
    input: 'tz-input'
  }
});

function swalFire(options) {
  const userDidOpen = options?.didOpen;
  const userDidClose = options?.didClose;
  return SwalTZ.fire({
    ...options,
    didOpen: (popup) => {
      document.body.classList.add('tz-blur');
      try {
        setTimeout(() => map?.invalidateSize?.(), 50);
      } catch {}
      if (typeof userDidOpen === 'function') userDidOpen(popup);
    },
    didClose: () => {
      document.body.classList.remove('tz-blur');
      try {
        setTimeout(() => map?.invalidateSize?.(), 50);
      } catch {}
      if (typeof userDidClose === 'function') userDidClose();
    }
  });
}

async function apiGet(action, params = {}) {
  const url = new URL('api.php', window.location.href);
  url.searchParams.set('action', action);
  for (const [k, v] of Object.entries(params)) {
    url.searchParams.set(k, v);
  }
  const res = await fetch(url.toString(), { credentials: 'same-origin' });
  const json = await res.json();
  if (!json.success) throw new Error(json.error || 'Request failed');
  return json.data;
}

async function apiPost(action, payload = {}) {
  const url = new URL('api.php', window.location.href);
  url.searchParams.set('action', action);
  const res = await fetch(url.toString(), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(payload)
  });
  const json = await res.json();
  if (!json.success) throw new Error(json.error || 'Request failed');
  return json.data;
}

function starsText(rating) {
  const r = Math.max(0, Math.min(5, Number(rating || 0)));
  const full = Math.floor(r);
  const half = (r - full) >= 0.5;
  let s = '★'.repeat(full);
  if (half) s += '☆';
  s += '✩'.repeat(Math.max(0, 5 - s.length));
  return s;
}

let session = { logged_in: false, user: null };
let business = { logged_in: false, account: null, business: null };
let map;
let markers = new Map();
let saleStream = null;
let currentBusinessModalId = null;
let countdownInterval = null;
let pushInitAttempted = false;

let refreshSessionInFlight = null;
let lastSessionRefreshAt = 0;

const SwalToast = SwalTZ.mixin({
  toast: true,
  position: 'top',
  showConfirmButton: false,
  timer: 7000,
  timerProgressBar: true,
  backdrop: false,
});

function tzDebugEnabled() {
  try {
    return window.TZ_DEBUG === true || localStorage.getItem('tz_debug') === '1';
  } catch {
    return window.TZ_DEBUG === true;
  }
}

function tzLog(...args) {
  if (!tzDebugEnabled()) return;
  console.log('[TimeZup]', new Date().toISOString(), ...args);
}

function tzWarn(...args) {
  if (!tzDebugEnabled()) return;
  console.warn('[TimeZup]', new Date().toISOString(), ...args);
}

function tzSummarizeEndpoint(endpoint) {
  try {
    const u = new URL(String(endpoint || ''), window.location.href);
    return `${u.protocol}//${u.host}${u.pathname ? '/…' : ''}`;
  } catch {
    const s = String(endpoint || '');
    return s ? `${s.slice(0, 32)}… (${s.length})` : '';
  }
}

function tzSummarizePushSubscription(subJson) {
  if (!subJson || typeof subJson !== 'object') return null;
  const endpoint = String(subJson.endpoint || '');
  const keys = (subJson.keys && typeof subJson.keys === 'object') ? subJson.keys : {};
  const p256dh = String(keys.p256dh || '');
  const auth = String(keys.auth || '');
  return {
    endpoint: tzSummarizeEndpoint(endpoint),
    p256dh_len: p256dh.length,
    auth_len: auth.length,
    has_keys: !!(p256dh && auth)
  };
}

function stopSaleStream() {
  try {
    saleStream?.close?.();
  } catch {}
  saleStream = null;
}

function startSaleStream() {
  stopSaleStream();
  if (!session.logged_in) return;

  try {
    saleStream = new EventSource('stream.php', { withCredentials: true });
  } catch (e) {
    console.warn('Unable to start sale stream', e);
    return;
  }

  saleStream.addEventListener('sale', async (ev) => {
    try {
      const payload = JSON.parse(ev.data);
      const message = String(payload.message || 'Sale started');

      await SwalToast.fire({
        icon: 'info',
        title: 'Sale',
        text: message,
      });

      if (currentBusinessModalId && String(payload.business_id) === String(currentBusinessModalId)) {
        try { Swal.close(); } catch {}
        await openBusinessModal(currentBusinessModalId);
      }
    } catch (e) {
      console.warn('Bad sale event', e);
    }
  });
}

function urlBase64ToUint8Array(base64String) {
  // base64url -> base64
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replaceAll('-', '+').replaceAll('_', '/');
  const rawData = atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}

async function ensurePushSubscribed({ prompt = false } = {}) {
  if (!session.logged_in) return;
  if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) return;

  tzLog('ensurePushSubscribed()', {
    prompt,
    isSecureContext: !!window.isSecureContext,
    permission: Notification.permission,
  });

  // Push + service workers require a secure context (HTTPS) except on localhost.
  if (!window.isSecureContext) {
    tzWarn('Push requires secure context (HTTPS or localhost).', { href: window.location.href });
    if (prompt && !pushInitAttempted) {
      pushInitAttempted = true;
      try {
        await swalFire({
          icon: 'info',
          title: 'Notifications need HTTPS',
          html: `
            <div class="tz-muted" style="text-align:left;">
              <div style="margin-bottom:10px;">To get notifications when Chrome is closed, TimeZup must be opened over <b>HTTPS</b>.</div>
              <div style="margin-bottom:10px;">Your current address is:</div>
              <div style="padding:10px;border:1px solid var(--tz-border);border-radius:12px;background:#fff;word-break:break-all;">${escapeHtml(window.location.href)}</div>
              <div style="margin-top:10px;">Tip: <code>http://localhost</code> is allowed, but <code>http://192.168.x.x</code> is not.</div>
            </div>
          `,
          confirmButtonText: 'Ok'
        });
      } catch {}
    }
    return;
  }

  let permission = Notification.permission;
  if (permission === 'default' && prompt) {
    try {
      permission = await Notification.requestPermission();
    } catch {
      permission = Notification.permission;
    }
  }
  if (permission !== 'granted') return;

  let reg;
  try {
    reg = await navigator.serviceWorker.register('sw.js');
    tzLog('Service worker registered', { scope: reg?.scope || null });
  } catch (e) {
    console.warn('Service worker registration failed', e);
    tzWarn('Service worker registration failed', { message: e?.message || String(e) });
    return;
  }

  let sub;
  try {
    sub = await reg.pushManager.getSubscription();
    tzLog('Existing push subscription', { exists: !!sub });
  } catch (e) {
    console.warn('Unable to read existing push subscription', e);
    tzWarn('Unable to read existing push subscription', { message: e?.message || String(e) });
    sub = null;
  }
  if (!sub) {
    let keyData;
    try {
      keyData = await apiGet('push_public_key');
      tzLog('Fetched VAPID public key', { length: String(keyData.public_key || '').length });
    } catch (e) {
      console.warn('Unable to fetch VAPID key', e);
      tzWarn('Unable to fetch VAPID key', { message: e?.message || String(e) });
      return;
    }

    const publicKey = String(keyData.public_key || '').trim();
    if (!publicKey) return;

    try {
      sub = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(publicKey)
      });
      tzLog('Created new push subscription', tzSummarizePushSubscription(sub?.toJSON?.()));
    } catch (e) {
      console.warn('Push subscribe failed', e);
      tzWarn('Push subscribe failed', { message: e?.message || String(e) });
      return;
    }
  }

  try {
    tzLog('Saving push subscription to server', tzSummarizePushSubscription(sub?.toJSON?.()));
    await apiPost('push_subscribe', { subscription: sub.toJSON() });
    tzLog('push_subscribe saved OK');
    if (tzDebugEnabled()) {
      try {
        const status = await apiGet('debug_push_status');
        tzLog('debug_push_status (after push_subscribe)', status);
      } catch (e) {
        tzWarn('debug_push_status FAILED (after push_subscribe)', { message: e?.message || String(e) });
      }
    }
    if (prompt) {
      try {
        await SwalToast.fire({
          icon: 'success',
          title: 'Notifications enabled',
          text: 'You will now get sale notifications even if Chrome is closed.'
        });
      } catch {}
    }
  } catch (e) {
    console.warn('Push subscription save failed', e);
    tzWarn('Push subscription save failed', { message: e?.message || String(e) });
  }
}

async function refreshSession({ force = false } = {}) {
  const now = Date.now();
  // Avoid spamming the server and making the UI feel "randomly slow".
  // Many UI actions call refreshSession(); coalesce calls happening close together.
  if (!force) {
    if (refreshSessionInFlight) return refreshSessionInFlight;
    if (now - lastSessionRefreshAt < 800) return;
  }

  refreshSessionInFlight = (async () => {
    const [userSession, bizSession] = await Promise.all([
      apiGet('session'),
      apiGet('business_session')
    ]);
    session = userSession;
    business = bizSession;
    lastSessionRefreshAt = Date.now();
  })();

  try {
    await refreshSessionInFlight;
  } finally {
    refreshSessionInFlight = null;
  }

  const btnSignup = document.getElementById('btnSignup');
  const btnLogin = document.getElementById('btnLogin');
  const btnProfile = document.getElementById('btnProfile');
  const btnBusiness = document.getElementById('btnBusiness');

  if (session.logged_in) {
    btnSignup.style.display = 'none';
    btnLogin.style.display = 'none';
    btnProfile.textContent = 'My profile';
  } else {
    btnSignup.style.display = '';
    btnLogin.style.display = '';
    btnProfile.textContent = 'My profile';
  }

  if (business.logged_in) {
    btnBusiness.textContent = 'Manage business';
  } else {
    btnBusiness.textContent = 'Business login';
  }

  if (session.logged_in) {
    startSaleStream();
    // Keep push subscription refreshed (no permission prompt here)
    ensurePushSubscribed({ prompt: false });
  } else {
    stopSaleStream();
  }
}

async function showSignup() {
  const html = `
    <form class="tz-form" id="tzSignupForm">
      <div class="tz-row">
        <input class="tz-input" name="name" placeholder="Name" autocomplete="given-name" required />
        <input class="tz-input" name="surname" placeholder="Surname" autocomplete="family-name" required />
      </div>
      <input class="tz-input" type="email" name="email" placeholder="Email" autocomplete="email" required />
      <input class="tz-input" type="password" name="password" placeholder="Password (min 6 chars)" autocomplete="new-password" required />
    </form>
  `;

  const result = await swalFire({
    title: 'Sign up',
    html,
    focusConfirm: false,
    showCancelButton: true,
    preConfirm: async () => {
      const form = document.getElementById('tzSignupForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.name || !data.surname || !data.email || !data.password) {
        Swal.showValidationMessage('Please fill in all fields');
        return;
      }
      try {
        await apiPost('signup', data);
      } catch (e) {
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (result.isConfirmed) {
    await refreshSession();
    await ensurePushSubscribed({ prompt: true });
    await swalFire({ title: 'Welcome!', text: 'Your account is ready.', icon: 'success' });
  }
}

async function showLogin() {
  const html = `
    <form class="tz-form" id="tzLoginForm">
      <input class="tz-input" type="email" name="email" placeholder="Email" autocomplete="email" required />
      <input class="tz-input" type="password" name="password" placeholder="Password" autocomplete="current-password" required />
    </form>
  `;

  const result = await swalFire({
    title: 'Log in',
    html,
    focusConfirm: false,
    showCancelButton: true,
    preConfirm: async () => {
      const form = document.getElementById('tzLoginForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.email || !data.password) {
        Swal.showValidationMessage('Enter email and password');
        return;
      }
      try {
        await apiPost('login', data);
      } catch (e) {
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (result.isConfirmed) {
    await refreshSession();
    await ensurePushSubscribed({ prompt: true });
    await swalFire({ title: 'Logged in', icon: 'success', timer: 900, showConfirmButton: false });
  }
}

async function showProfile() {
  await refreshSession({ force: true });
  if (!session.logged_in) {
    await showLogin();
    await refreshSession({ force: true });
    if (!session.logged_in) return;
  }

  const u = session.user;
  const html = `
    <form class="tz-form" id="tzProfileForm">
      <div class="tz-row">
        <input class="tz-input" name="name" placeholder="Name" value="${escapeHtml(u.name || '')}" required />
        <input class="tz-input" name="surname" placeholder="Surname" value="${escapeHtml(u.surname || '')}" required />
      </div>
      <input class="tz-input" type="email" name="email" placeholder="Email" value="${escapeHtml(u.email || '')}" required />
      <input class="tz-input" type="password" name="new_password" placeholder="New password (optional)" autocomplete="new-password" />
      <button class="tz-btn" type="button" id="tzReEnablePush">Re-enable notifications on this device</button>
      <a class="tz-link" href="#" id="tzManageSubs">Manage subscriptions</a>
      <div class="tz-muted" style="font-size: 12px;">Your unique ID is stored but not displayed.</div>
    </form>
  `;

  const result = await swalFire({
    title: 'My profile',
    html,
    focusConfirm: false,
    showCancelButton: true,
    showDenyButton: true,
    denyButtonText: 'Log out',
    confirmButtonText: 'Save',
    didOpen: (popup) => {
      const link = popup.querySelector('#tzManageSubs');
      link?.addEventListener('click', async (ev) => {
        ev.preventDefault();
        Swal.close();
        await showManageSubscriptions();
      });

      const btn = popup.querySelector('#tzReEnablePush');
      btn?.addEventListener('click', async (ev) => {
        ev.preventDefault();
        try {
          btn.disabled = true;
          btn.textContent = 'Enabling…';
          await ensurePushSubscribed({ prompt: true });
          await swalFire({
            title: 'Notifications updated',
            icon: 'success',
            text: 'If this device supports push, it is now registered.'
          });
        } catch (e) {
          await swalFire({ title: 'Error', icon: 'error', text: e?.message || String(e) });
        } finally {
          btn.disabled = false;
          btn.textContent = 'Re-enable notifications on this device';
        }
      });
    },
    preConfirm: async () => {
      const form = document.getElementById('tzProfileForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.name || !data.surname || !data.email) {
        Swal.showValidationMessage('Fill in name, surname and email');
        return;
      }
      try {
        await apiPost('update_profile', data);
      } catch (e) {
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (result.isDenied) {
    await apiPost('logout');
    await refreshSession();
    await swalFire({ title: 'Logged out', icon: 'success', timer: 900, showConfirmButton: false });
    return;
  }

  if (result.isConfirmed) {
    await refreshSession();
    await swalFire({ title: 'Saved', icon: 'success', timer: 900, showConfirmButton: false });
  }
}

async function showManageSubscriptions() {
  await refreshSession({ force: true });
  if (!session.logged_in) {
    await showLogin();
    await refreshSession({ force: true });
    if (!session.logged_in) return;
  }

  const data = await apiGet('my_subscriptions');
  const subs = data.subscriptions || [];

  const rows = subs.map((s) => {
    const badge = s.icon_url
      ? `<img src="${escapeHtml(s.icon_url)}" alt="" style="width:18px;height:18px;object-fit:contain;border-radius:999px;background:#fff;border:1px solid var(--tz-border);padding:2px;" />`
      : `<span style="width:18px;height:18px;display:inline-grid;place-items:center;border-radius:999px;background:#fff;border:1px solid var(--tz-border);font-weight:900;font-size:11px;">${escapeHtml((s.name || '?').slice(0,1).toUpperCase())}</span>`;

    return `
      <div style="display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px; border:1px solid var(--tz-border); border-radius:12px;">
        <div style="display:flex; align-items:center; gap:10px; min-width:0;">
          ${badge}
          <div style="font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(s.name || '')}</div>
        </div>
        <button class="tz-btn tz-danger" type="button" data-bid="${escapeHtml(s.business_id)}">Unsubscribe</button>
      </div>
    `;
  }).join('');

  await swalFire({
    title: 'My subscriptions',
    html: rows || `<div class="tz-muted">You are not subscribed to any businesses yet.</div>`,
    showConfirmButton: false,
    showCancelButton: false,
    didOpen: (popup) => {
      popup.querySelectorAll('[data-bid]')?.forEach((btn) => {
        btn.addEventListener('click', async () => {
          const bid = btn.getAttribute('data-bid');
          if (!bid) return;
          try {
            tzLog('subscription_toggle (from manage subs)', { business_id: bid });
            await apiPost('subscription_toggle', { business_id: bid });
            tzLog('subscription_toggle OK (from manage subs)', { business_id: bid });
            Swal.close();
            await showManageSubscriptions();
          } catch (e) {
            tzWarn('subscription_toggle FAILED (from manage subs)', { business_id: bid, message: e?.message || String(e) });
            await swalFire({ title: 'Error', text: e.message || 'Request failed', icon: 'error' });
          }
        });
      });
    }
  });
}

async function showBusinessLogin() {
  const html = `
    <form class="tz-form" id="tzBizLoginForm">
      <input class="tz-input" type="email" name="email" placeholder="Business email" autocomplete="email" required />
      <input class="tz-input" type="password" name="password" placeholder="Password" autocomplete="current-password" required />
      <div class="tz-muted" style="font-size:12px;">Seed logins: cafe@timezup.biz / gym@timezup.biz / spa@timezup.biz (password: TimeZup123!).</div>
    </form>
  `;

  const result = await swalFire({
    title: 'Business login',
    html,
    showCancelButton: true,
    focusConfirm: false,
    preConfirm: async () => {
      const form = document.getElementById('tzBizLoginForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.email || !data.password) {
        Swal.showValidationMessage('Enter email and password');
        return;
      }
      try {
        await apiPost('business_login', data);
      } catch (e) {
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (result.isConfirmed) {
    await refreshSession();
    await showManageBusiness();
  }
}

async function showManageBusiness() {
  await refreshSession({ force: true });
  if (!business.logged_in) {
    await showBusinessLogin();
    await refreshSession({ force: true });
    if (!business.logged_in) return;
  }

  const b = business.business || {};
  const html = `
    <form class="tz-form" id="tzBizManageForm">
      <input class="tz-input" name="name" placeholder="Business name" value="${escapeHtml(b.name || '')}" required />
      <input class="tz-input" name="address" placeholder="Address" value="${escapeHtml(b.address || '')}" required />
      <textarea class="tz-textarea" name="description" placeholder="Description" rows="4" required>${escapeHtml(b.description || '')}</textarea>
      <div class="tz-row">
        <input class="tz-input" name="lat" placeholder="Latitude" value="${escapeHtml(String(b.lat ?? ''))}" required />
        <input class="tz-input" name="lng" placeholder="Longitude" value="${escapeHtml(String(b.lng ?? ''))}" required />
      </div>
      <div class="tz-row">
        <input class="tz-input" name="pin_color" placeholder="Pin color (hex, e.g. #2e9b4a)" value="${escapeHtml(b.pin_color || '')}" />
        <input class="tz-input" name="icon_url" placeholder="Logo icon URL (shows on pin)" value="${escapeHtml(b.icon_url || '')}" />
      </div>
      <input class="tz-input" name="image_url" placeholder="Image URL" value="${escapeHtml(b.image_url || '')}" />

      <button id="tzStartSaleBtn" class="tz-btn tz-btn-primary" type="button">Start Sale</button>
    </form>
  `;

  const result = await swalFire({
    title: 'Manage business',
    html,
    showCancelButton: true,
    showDenyButton: true,
    denyButtonText: 'Log out',
    confirmButtonText: 'Save',
    focusConfirm: false,
    didOpen: (popup) => {
      const startBtn = popup.querySelector('#tzStartSaleBtn');
      startBtn?.addEventListener('click', async () => {
        await showStartSale();
      });
    },
    preConfirm: async () => {
      const form = document.getElementById('tzBizManageForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.name || !data.address || !data.description || !data.lat || !data.lng) {
        Swal.showValidationMessage('Fill in all required fields');
        return;
      }
      try {
        await apiPost('business_update_my', data);
      } catch (e) {
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (result.isDenied) {
    await apiPost('business_logout');
    await refreshSession();
    await swalFire({ title: 'Business logged out', icon: 'success', timer: 900, showConfirmButton: false });
    return;
  }

  if (result.isConfirmed) {
    await refreshSession();
    await loadBusinesses();
    await swalFire({ title: 'Saved', icon: 'success', timer: 900, showConfirmButton: false });
  }
}

async function showStartSale() {
  await refreshSession({ force: true });
  if (!business.logged_in) {
    await showBusinessLogin();
    await refreshSession({ force: true });
    if (!business.logged_in) return;
  }

  const html = `
    <form class="tz-form" id="tzSaleForm">
      <textarea class="tz-textarea" name="message" rows="4" placeholder="Type your sale message" required></textarea>
      <input class="tz-input" name="duration_minutes" type="number" min="1" max="1440" value="90" required />
      <div class="tz-muted" style="font-size:12px;">Duration in minutes (e.g. 90).</div>
    </form>
  `;

  const res = await swalFire({
    title: 'Start Sale',
    html,
    showCancelButton: true,
    focusConfirm: false,
    confirmButtonText: 'Start',
    preConfirm: async () => {
      const form = document.getElementById('tzSaleForm');
      const data = Object.fromEntries(new FormData(form).entries());
      if (!data.message || !data.duration_minutes) {
        Swal.showValidationMessage('Enter a message and duration');
        return;
      }
      try {
        tzLog('business_start_sale request', {
          duration_minutes: String(data.duration_minutes),
          message_len: String(data.message || '').length,
        });
        return await apiPost('business_start_sale', {
          message: String(data.message),
          duration_minutes: String(data.duration_minutes)
        });
      } catch (e) {
        tzWarn('business_start_sale FAILED', { message: e?.message || String(e) });
        Swal.showValidationMessage(e.message);
        return;
      }
      return true;
    }
  });

  if (res.isConfirmed) {
    const out = res.value || null;
    const push = out && out.push ? out.push : null;

    tzLog('business_start_sale response', {
      business_id: out?.business_id || null,
      end_at: out?.end_at || null,
      push,
    });

    if (tzDebugEnabled()) {
      try {
        const status = await apiGet('debug_push_status');
        tzLog('debug_push_status', status);
      } catch (e) {
        tzWarn('debug_push_status FAILED', { message: e?.message || String(e) });
      }
    }

    let pushText = '';
    if (push && typeof push === 'object') {
      if (push.error) {
        try {
          console.warn('[TimeZup] Push error from business_start_sale:', push);
        } catch {}
        pushText = 'Push failed (see browser console for details).';
      } else if (Number.isFinite(push.attempted)) {
        const attempted = Number(push.attempted || 0);
        const sent = Number(push.sent || 0);
        const failed = Number(push.failed || 0);
        if (attempted <= 0) {
          pushText = 'Push: 0 devices registered (users must log in over HTTPS on their phones).';
        } else {
          pushText = `Push: sent ${sent}/${attempted} (failed: ${failed}).`;
        }
      }
    }

    await loadBusinesses();
    await swalFire({
      title: 'Sale started',
      icon: 'success',
      text: pushText || undefined,
      timer: 2500,
      showConfirmButton: false
    });
  }
}

function escapeHtml(s) {
  return String(s)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}

function formatRemaining(seconds) {
  const s = Math.max(0, Math.floor(Number(seconds) || 0));
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const ss = s % 60;
  if (h > 0) return `${h}h ${String(m).padStart(2, '0')}m ${String(ss).padStart(2, '0')}s`;
  return `${m}m ${String(ss).padStart(2, '0')}s`;
}

function setupCountdown(endAtIso) {
  if (!endAtIso) return;
  const endTs = Date.parse(endAtIso);
  if (!Number.isFinite(endTs)) return;

  if (countdownInterval) {
    clearInterval(countdownInterval);
    countdownInterval = null;
  }

  const tick = () => {
    const now = Date.now();
    const remaining = Math.max(0, Math.floor((endTs - now) / 1000));
    const el = document.getElementById('tzSaleCountdown');
    if (el) {
      el.textContent = formatRemaining(remaining);
    }
    if (remaining <= 0) {
      clearInterval(countdownInterval);
      countdownInterval = null;
    }
  };

  tick();
  countdownInterval = setInterval(tick, 1000);
}

async function openBusinessModal(businessId) {
  // This runs on every pin click.
  // Avoid extra roundtrips here: `business_get` already returns `logged_in` + `subscribed`.
  const data = await apiGet('business_get', { id: businessId });
  const b = data.business;
  const sale = data.sale;

  tzLog('business_get', {
    business_id: businessId,
    logged_in: !!data.logged_in,
    subscribed: !!data.subscribed,
    isSecureContext: !!window.isSecureContext,
    notification_permission: (typeof Notification !== 'undefined') ? Notification.permission : null,
  });

  currentBusinessModalId = businessId;

  const subscribeLabel = data.logged_in
    ? (data.subscribed ? 'Unsubscribe' : 'Subscribe')
    : 'Log in to subscribe';

  const html = `
    <div class="tz-biz">
      ${b.image_url ? `<img src="${escapeHtml(b.image_url)}" alt="${escapeHtml(b.name)}" />` : ''}
      <div style="display:flex; align-items:flex-start; justify-content:space-between; gap:10px;">
        <div>
          <div style="font-size:18px; font-weight:800;">${escapeHtml(b.name || '')}</div>
          <div class="tz-muted" style="margin-top:2px;">${escapeHtml(b.address || '')}</div>
        </div>
        <div class="tz-rating" title="Average rating">
          <div class="tz-stars">${starsText(b.avg_rating)}</div>
          <div class="tz-muted" style="font-weight:700;">${Number(b.avg_rating || 0).toFixed(1)}</div>
        </div>
      </div>
      <div>${escapeHtml(b.description || '')}</div>

      ${sale ? `
        <div class="tz-sale" style="margin-top:12px;">
          <div class="tz-sale-title">Sale is live</div>
          <div style="margin-bottom:6px;">${escapeHtml(sale.message || '')}</div>
          <div class="tz-muted">Ends in <span id="tzSaleCountdown" class="tz-sale-timer"></span></div>
        </div>
      ` : ''}

      <div style="display:flex; gap:10px; margin-top:12px;">
        <button id="tzSubBtn" class="tz-btn tz-btn-primary" type="button">${subscribeLabel}</button>
        <button id="tzCloseBtn" class="tz-btn" type="button">Close</button>
      </div>
    </div>
  `;

  await swalFire({
    title: 'Business',
    html,
    showConfirmButton: false,
    showCancelButton: false,
    didClose: () => {
      currentBusinessModalId = null;
      if (countdownInterval) {
        clearInterval(countdownInterval);
        countdownInterval = null;
      }
    },
    didOpen: (popup) => {
      const closeBtn = popup.querySelector('#tzCloseBtn');
      const subBtn = popup.querySelector('#tzSubBtn');

      if (sale && sale.end_at) {
        setupCountdown(sale.end_at);
      }

      closeBtn?.addEventListener('click', () => Swal.close());

      subBtn?.addEventListener('click', async () => {
        try {
          await refreshSession({ force: true });
          if (!session.logged_in) {
            Swal.close();
            await showLogin();
            await refreshSession({ force: true });
            if (!session.logged_in) return;
            // Re-open business details (now with a real subscribe/unsubscribe state)
            await openBusinessModal(businessId);
            return;
          }

          tzLog('subscription_toggle (from business modal)', { business_id: businessId });
          const out = await apiPost('subscription_toggle', { business_id: businessId });
          const subscribedNow = !!out.subscribed;
          tzLog('subscription_toggle OK (from business modal)', { business_id: businessId, subscribed: subscribedNow });
          subBtn.textContent = subscribedNow ? 'Unsubscribe' : 'Subscribe';
          await swalFire({
            title: subscribedNow ? 'Subscribed' : 'Unsubscribed',
            icon: 'success',
            timer: 900,
            showConfirmButton: false
          });

          // Helpful correlation: after subscribing, ensure we have a push subscription saved too.
          // (No permission prompt unless the user explicitly enabled debug and wants to see what's happening.)
          if (tzDebugEnabled()) {
            await ensurePushSubscribed({ prompt: true });
          }
        } catch (e) {
          tzWarn('subscription_toggle FAILED (from business modal)', { business_id: businessId, message: e?.message || String(e) });
          await swalFire({ title: 'Error', text: e.message || 'Request failed', icon: 'error' });
        }
      });
    }
  });
}

function safePinColor(value) {
  const v = String(value || '').trim();
  if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(v)) return v;
  return 'var(--tz-primary)';
}

function makeBusinessDivIcon(business) {
  const color = safePinColor(business.pin_color);
  const iconUrl = String(business.icon_url || '').trim();
  const name = String(business.name || '').trim();
  const fallback = name ? name.slice(0, 1).toUpperCase() : '?';

  const imgHtml = `
    <div class="tz-pin-fallback" style="display:${iconUrl ? 'none' : 'grid'};">${escapeHtml(fallback)}</div>
    ${iconUrl ? `<img src="${escapeHtml(iconUrl)}" alt="" referrerpolicy="no-referrer" onerror="this.style.display='none'; this.parentNode.querySelector('.tz-pin-fallback').style.display='grid';" />` : ''}
  `;

  const html = `
    <div class="tz-pin" style="--tz-pin-color:${escapeHtml(color)}">
      <div class="tz-pin-head">${imgHtml}</div>
    </div>
  `;

  return L.divIcon({
    className: 'tz-div-icon',
    html,
    iconSize: [44, 54],
    iconAnchor: [22, 52]
  });
}

async function loadBusinesses() {
  const data = await apiGet('businesses_list');
  const businesses = data.businesses || [];

  // Clear old markers
  for (const m of markers.values()) {
    m.remove();
  }
  markers.clear();

  businesses.forEach((b) => {
    const lat = Number(b.lat);
    const lng = Number(b.lng);
    if (!Number.isFinite(lat) || !Number.isFinite(lng)) return;

    const marker = L.marker([lat, lng], { icon: makeBusinessDivIcon(b) });
    marker.addTo(map);
    marker.on('click', () => {
      openBusinessModal(b.id).catch(async (e) => {
        console.warn('Unable to open business modal', e);
        try {
          await swalFire({ title: 'Error', text: e.message || 'Request failed', icon: 'error' });
        } catch {}
      });
    });
    markers.set(b.id, marker);
  });
}

async function init() {
  map = L.map('map').setView([window.TZ_CONFIG.centerLat, window.TZ_CONFIG.centerLng], window.TZ_CONFIG.zoom);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; OpenStreetMap contributors'
  }).addTo(map);

  document.getElementById('btnSignup').addEventListener('click', showSignup);
  document.getElementById('btnLogin').addEventListener('click', showLogin);
  document.getElementById('btnProfile').addEventListener('click', showProfile);
  document.getElementById('btnBusiness').addEventListener('click', async () => {
    await refreshSession({ force: true });
    if (business.logged_in) {
      await showManageBusiness();
    } else {
      await showBusinessLogin();
    }
  });

  await refreshSession({ force: true });
  await loadBusinesses();
}

init().catch(async (e) => {
  console.error(e);
  await swalFire({ title: 'Error', text: e.message || 'Something went wrong', icon: 'error' });
});
