import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
import {
  fetchLocations,
  fetchCategories,
  fetchServices,
  fetchExtras,
  fetchAgents,
  fetchFormFields,
  createBooking,
  startWooCheckout,
  startStripeCheckout,
  startPaypalCheckout,
} from './api';
import useBookingFormDesign from '../hooks/useBookingFormDesign';
import StepLocation from './steps/StepLocation';
import StepCategory from './steps/StepCategory';
import StepService from './steps/StepService';
import StepExtras from './steps/StepExtras';
import StepAgent from './steps/StepAgent';
import StepDateTime from './steps/StepDateTime';
import StepCustomer from './steps/StepCustomer';
import StepPayment from './steps/StepPayment';
import StepReview from './steps/StepReview';
import StepConfirmation from './steps/StepConfirmation';
import useBpFrontSettings from '../hooks/useBpFrontSettings';
import { formatMoney } from './money';
import { trapFocusWithin } from '../../shared/focusTrap';

const REQUIRED_STEP_ORDER = [
  'location',
  'category',
  'service',
  'extras',
  'agent',
  'datetime',
  'customer',
  'review',
  'payment',
  'confirmation',
];

const DEFAULT_STEPS = [
  { key: 'location', title: 'Location Selection', icon: 'locations.svg' },
  { key: 'category', title: 'Choose Category', icon: 'categories.svg' },
  { key: 'service', title: 'Choose Service', icon: 'services.svg' },
  { key: 'extras', title: 'Service Extras', icon: 'service-extras.svg' },
  { key: 'agent', title: 'Choose Agent', icon: 'agents.svg' },
  { key: 'datetime', title: 'Choose Date & Time', icon: 'calendar.svg' },
  { key: 'customer', title: 'Customer Information', icon: 'customers.svg' },
  { key: 'review', title: 'Review Order', icon: 'bookings.svg' },
  { key: 'payment', title: 'Payment', icon: 'payment.svg' },
  { key: 'confirmation', title: 'Confirm', icon: 'logo.png' },
];

const CANONICAL_ORDER = REQUIRED_STEP_ORDER.slice();
const REQUIRED_KEYS = new Set(['service', 'agent', 'datetime', 'customer', 'review', 'confirmation']);
const SHORTCODE_SKIPPABLE_KEYS = new Set(['location', 'category', 'service', 'extras', 'agent']);
const PAYMENT_METHOD_LABELS = {
  free: 'Free booking',
  cash: 'Pay at location',
  woocommerce: 'Card or gateway checkout',
  stripe: 'Secure card payment',
  paypal: 'PayPal',
};
const KNOWN_PAYMENT_METHODS = new Set(Object.keys(PAYMENT_METHOD_LABELS));

function normalizeKey(k) {
  if (!k) return '';
  if (k === 'agents') return 'agent';
  if (k === 'agent') return 'agent';
  if (k === 'done' || k === 'confirm') return 'confirmation';
  if (k === 'confirmation') return 'confirmation';
  return k;
}

function toBoolEnabled(v) {
  return v !== false && v !== 0 && v !== '0';
}

function parseOptionalBool(value) {
  if (value === true || value === false) return value;
  if (value == null) return null;
  const normalized = String(value).trim().toLowerCase();
  if (!normalized) return null;
  if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
  if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
  return null;
}

function toPositiveInt(value) {
  const num = Number(value);
  if (!Number.isFinite(num)) return 0;
  const whole = Math.trunc(num);
  return whole > 0 ? whole : 0;
}

function sanitizeColor(value) {
  if (typeof value !== 'string') return '';
  const color = value.trim();
  if (!color) return '';
  if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) return color;
  if (/^rgba?\(\s*[\d.\s,%]+\s*\)$/i.test(color)) return color;
  if (/^hsla?\(\s*[\d.\s,%]+\s*\)$/i.test(color)) return color;
  return '';
}

function normalizeIdList(value) {
  if (!Array.isArray(value)) return [];
  const unique = new Set();
  value.forEach((item) => {
    const id = toPositiveInt(item);
    if (id > 0) unique.add(id);
  });
  return Array.from(unique);
}

function normalizeSkipKey(key) {
  const normalized = normalizeKey(String(key || '').trim().toLowerCase());
  return SHORTCODE_SKIPPABLE_KEYS.has(normalized) ? normalized : '';
}

function canShortcodeSkip(stepKey, preselect) {
  switch (stepKey) {
    case 'location':
      return preselect.locationId > 0;
    case 'category':
      return preselect.categoryIds.length > 0;
    case 'service':
      return preselect.serviceId > 0;
    case 'agent':
      return preselect.agentId > 0;
    case 'extras':
      return true;
    default:
      return false;
  }
}

function normalizePaymentMethod(value) {
  const method = String(value || '').trim().toLowerCase();
  return KNOWN_PAYMENT_METHODS.has(method) ? method : '';
}

function normalizePaymentMethods(value) {
  if (!Array.isArray(value)) return [];
  const unique = new Set();
  value.forEach((item) => {
    const method = normalizePaymentMethod(item);
    if (method) unique.add(method);
  });
  return Array.from(unique);
}

function getDefaultNextLabel(stepKey, { requiresPayment = false } = {}) {
  switch (stepKey) {
    case 'location':
      return 'Continue to categories';
    case 'category':
      return 'Continue to services';
    case 'service':
      return 'Continue to extras';
    case 'extras':
      return 'Continue to staff';
    case 'agent':
      return 'Continue to date & time';
    case 'datetime':
      return 'Continue to your details';
    case 'customer':
      return 'Review booking';
    case 'review':
      return requiresPayment ? 'Continue to payment' : 'Confirm booking';
    default:
      return 'Continue';
  }
}

function normalizeWidgetOptions(rawOptions) {
  const options = rawOptions && typeof rawOptions === 'object' ? rawOptions : {};
  const preselectRaw = options.preselect && typeof options.preselect === 'object' ? options.preselect : {};
  const widgetRaw = options.widget && typeof options.widget === 'object' ? options.widget : {};

  const preselect = {
    locationId: toPositiveInt(preselectRaw.locationId),
    categoryIds: normalizeIdList(preselectRaw.categoryIds),
    serviceId: toPositiveInt(preselectRaw.serviceId),
    agentId: toPositiveInt(preselectRaw.agentId),
  };

  const requestedSkip = Array.isArray(options.skipSteps) ? options.skipSteps : [];
  const skipUnique = new Set();
  requestedSkip.forEach((item) => {
    const key = normalizeSkipKey(item);
    if (!key) return;
    if (!canShortcodeSkip(key, preselect)) return;
    skipUnique.add(key);
  });

  const widget = {};
  const primaryColor = sanitizeColor(widgetRaw.primaryColor);
  if (primaryColor) widget.primaryColor = primaryColor;

  const overlayColor = sanitizeColor(widgetRaw.overlayColor);
  if (overlayColor) widget.overlayColor = overlayColor;

  const backgroundColor = sanitizeColor(widgetRaw.backgroundColor);
  if (backgroundColor) widget.backgroundColor = backgroundColor;

  const sideBackgroundColor = sanitizeColor(widgetRaw.sideBackgroundColor);
  if (sideBackgroundColor) widget.sideBackgroundColor = sideBackgroundColor;

  const mainBackgroundColor = sanitizeColor(widgetRaw.mainBackgroundColor);
  if (mainBackgroundColor) widget.mainBackgroundColor = mainBackgroundColor;

  const borderRadius = toPositiveInt(widgetRaw.borderRadius);
  if (borderRadius > 0) widget.borderRadius = Math.min(80, borderRadius);

  const maxWidth = toPositiveInt(widgetRaw.maxWidth);
  if (maxWidth >= 420) widget.maxWidth = Math.min(1800, maxWidth);

  const showLeftPanel = parseOptionalBool(widgetRaw.showLeftPanel);
  if (showLeftPanel !== null) widget.showLeftPanel = showLeftPanel;

  const showSummary = parseOptionalBool(widgetRaw.showSummary);
  if (showSummary !== null) widget.showSummary = showSummary;

  return {
    preselect,
    skipSteps: Array.from(skipUnique),
    widget,
  };
}

function resolveSummaryVisibility(rawLayoutValue) {
  const bool = parseOptionalBool(rawLayoutValue);
  if (bool !== null) return bool;
  return true;
}

function getRestBase() {
  const url = window.pointlybooking_FRONT?.restUrl || '/wp-json/pointly-booking/v1';
  return url.replace(/\/$/, '');
}

function extractManageKey(maybeUrl) {
  if (!maybeUrl) return '';
  try {
    const parsed = new URL(maybeUrl, window.location.origin);
    return parsed.searchParams.get('key') || '';
  } catch (e) {
    return '';
  }
}

async function paypalCapture(orderId, bookingId, bookingKey) {
  const r = await fetch(`${getRestBase()}/front/payments/paypal/capture`, {
    method: 'POST',
    credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ order_id: orderId, booking_id: bookingId, key: bookingKey }),
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(j?.message || 'PayPal capture failed');
  return j;
}

async function fetchBookingStatus(id, bookingKey) {
  const params = new URLSearchParams({ _t: String(Date.now()) });
  if (bookingKey) params.set('key', bookingKey);

  const r = await fetch(`${getRestBase()}/front/bookings/${id}/status?${params.toString()}`, {
    credentials: 'same-origin',
    headers: { 'X-WP-Nonce': window.pointlybooking_FRONT?.nonce || '' },
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok || !j?.success) throw new Error(j?.message || 'Could not load booking status');
  return j.booking;
}

async function waitForConfirmed(bookingId, bookingKey) {
  let last = null;
  for (let i = 0; i < 10; i++) {
    const b = await fetchBookingStatus(bookingId, bookingKey);
    last = b;
    if (b?.status === 'confirmed' && b?.payment_status === 'paid') return b;
    await new Promise((res) => setTimeout(res, 1000));
  }
  return last;
}

function clearPaymentQuery() {
  const url = new URL(window.location.href);
  url.searchParams.delete('pointlybooking_payment');
  url.searchParams.delete('booking_id');
  url.searchParams.delete('token');
  url.searchParams.delete('key');
  window.history.replaceState({}, '', url.toString());
}

const DRAFT_STORAGE_KEY = 'pointlybooking_booking_draft_v1';
const DRAFT_MAX_AGE_MS = 1000 * 60 * 60 * 72;

function createFrontSessionId() {
  if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
    return window.crypto.randomUUID();
  }
  return `bp-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
}

function canUseLocalStorage() {
  if (typeof window === 'undefined' || !window.localStorage) return false;
  try {
    const probeKey = '__bp_draft_probe__';
    window.localStorage.setItem(probeKey, '1');
    window.localStorage.removeItem(probeKey);
    return true;
  } catch (e) {
    return false;
  }
}

function readWizardDraft() {
  if (!canUseLocalStorage()) return null;
  try {
    const raw = window.localStorage.getItem(DRAFT_STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    return parsed && typeof parsed === 'object' ? parsed : null;
  } catch (e) {
    return null;
  }
}

function writeWizardDraft(payload) {
  if (!canUseLocalStorage()) return;
  try {
    window.localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(payload));
  } catch (e) {
    // Ignore storage failures.
  }
}

function clearWizardDraft() {
  if (!canUseLocalStorage()) return;
  try {
    window.localStorage.removeItem(DRAFT_STORAGE_KEY);
  } catch (e) {
    // Ignore storage failures.
  }
}

function hasResumeTokenInUrl() {
  if (typeof window === 'undefined') return '';
  try {
    const url = new URL(window.location.href);
    return url.searchParams.get('pointlybooking_resume') || '';
  } catch (e) {
    return '';
  }
}

function hasPaymentReturnInUrl() {
  if (typeof window === 'undefined') return false;
  try {
    const url = new URL(window.location.href);
    return !!url.searchParams.get('pointlybooking_payment');
  } catch (e) {
    return false;
  }
}

function clearResumeQuery() {
  if (typeof window === 'undefined') return;
  try {
    const url = new URL(window.location.href);
    url.searchParams.delete('pointlybooking_resume');
    window.history.replaceState({}, '', url.toString());
  } catch (e) {
    // Ignore URL parsing failures.
  }
}

function isDraftPayloadRestorable(draft) {
  if (!draft || typeof draft !== 'object') return false;
  const savedAt = Number(draft.savedAt || draft.saved_at_ts || 0);
  if (!savedAt || (Date.now() - savedAt) > DRAFT_MAX_AGE_MS) return false;
  if (draft.service_id || draft.location_id || draft.agent_id || draft.date) return true;
  if (Array.isArray(draft.category_ids) && draft.category_ids.length) return true;
  if (Array.isArray(draft.extra_ids) && draft.extra_ids.length) return true;
  if (draft.answers && typeof draft.answers === 'object' && Object.keys(draft.answers).length) return true;
  return false;
}

function resumeStepKey(stepKey) {
  const normalized = normalizeKey(stepKey || '');
  if (normalized === 'payment' || normalized === 'confirmation') {
    return 'review';
  }
  return normalized || 'service';
}

function formatDraftTime(savedAt) {
  const stamp = Number(savedAt || 0);
  if (!stamp) return 'recently';
  const diffMs = Math.max(0, Date.now() - stamp);
  const diffMin = Math.round(diffMs / 60000);
  if (diffMin < 2) return 'just now';
  if (diffMin < 60) return `${diffMin} min ago`;
  const diffHours = Math.round(diffMin / 60);
  if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
  const diffDays = Math.round(diffHours / 24);
  return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
}

function buildSteps(designConfig) {
  const baseMap = new Map(DEFAULT_STEPS.map((s) => [s.key, s]));
  const raw = Array.isArray(designConfig?.steps) ? designConfig.steps : [];

  const ordered = [];
  const seen = new Set();

  for (const s of raw) {
    const key = normalizeKey(s?.key);
    if (!key) continue;
    if (seen.has(key)) continue;
    seen.add(key);
    ordered.push({
      key,
      enabled: toBoolEnabled(s?.enabled),
      title: s?.title,
      subtitle: s?.subtitle,
      image: s?.image,
      buttonBackLabel: s?.buttonBackLabel,
      buttonNextLabel: s?.buttonNextLabel,
      accentOverride: s?.accentOverride,
      showLeftPanel: s?.showLeftPanel,
      showHelpBox: s?.showHelpBox,
    });
  }

  for (const d of DEFAULT_STEPS) {
    if (!seen.has(d.key)) {
      ordered.push({ key: d.key, enabled: true });
    }
  }

  const byKey = new Map(ordered.map((s) => [s.key, s]));

  const list = CANONICAL_ORDER
    .filter((k) => byKey.has(k))
    .map((key) => {
      const cfg = byKey.get(key) || {};
      const base = baseMap.get(key) || { key, title: key, icon: 'services.svg' };
      return {
        ...base,
        key,
        enabled: REQUIRED_KEYS.has(key) ? true : toBoolEnabled(cfg.enabled),
        title: (cfg.title != null && String(cfg.title).trim() !== '') ? String(cfg.title) : base.title,
        subtitle: (cfg.subtitle != null) ? String(cfg.subtitle) : '',
        icon: cfg.image || base.icon,
        image: cfg.image || base.icon,
        buttonBackLabel: cfg.buttonBackLabel,
        buttonNextLabel: cfg.buttonNextLabel,
        accentOverride: cfg.accentOverride,
        showLeftPanel: cfg.showLeftPanel,
        showHelpBox: cfg.showHelpBox,
      };
    })
    .filter((s) => !!s && (s.enabled !== false));

  return list;
}

export default function WizardModal({ open, onClose, brand, widgetOptions }) {
  const dialogTitleId = useId();
  const mobileSummaryPanelId = useId();
  const closeButtonRef = useRef(null);
  const restoreFocusRef = useRef(null);
  const modalRef = useRef(null);
  const mainRef = useRef(null);
  const headingRef = useRef(null);
  const closeRequestRef = useRef(() => {});
  const [stepIndex, setStepIndex] = useState(0);
  const [mobileSummaryOpen, setMobileSummaryOpen] = useState(false);
  const { config: designConfig, loading: designLoading, error: designError } = useBookingFormDesign(open);
  const shortcodeOptions = useMemo(() => normalizeWidgetOptions(widgetOptions), [widgetOptions]);
  const preselect = shortcodeOptions.preselect;
  const skipSteps = shortcodeOptions.skipSteps;
  const widgetStyleOptions = shortcodeOptions.widget;
  const skipStepsSig = skipSteps.join('|');
  const forcedSkipSet = useMemo(() => new Set(skipSteps), [skipStepsSig]);
  const baseSteps = useMemo(() => buildSteps(designConfig), [designConfig]);
  const { settings: bpSettings } = useBpFrontSettings(open);

  const [locationId, setLocationId] = useState(null);
  const [categoryIds, setCategoryIds] = useState([]);
  const [serviceId, setServiceId] = useState(null);
  const [extraIds, setExtraIds] = useState([]);
  const [agentId, setAgentId] = useState(null);
  const [date, setDate] = useState(null);
  const [slot, setSlot] = useState(null);
  const [paymentMethod, setPaymentMethod] = useState('');
  const [paymentBookingId, setPaymentBookingId] = useState(0);
  const [bookingId, setBookingId] = useState(null);
  const [bookingKey, setBookingKey] = useState('');
  const [isCreatingBooking, setIsCreatingBooking] = useState(false);
  const [createError, setCreateError] = useState(null);
  const [returnState, setReturnState] = useState({ mode: '', bookingId: 0, token: '', key: '', action: '' });
  const [returnLoading, setReturnLoading] = useState(false);
  const [returnError, setReturnError] = useState('');
  const [confirmData, setConfirmData] = useState(null);
  const [confirmInfo, setConfirmInfo] = useState(null);
  const [confirmLoading, setConfirmLoading] = useState(false);
  const [confirmError, setConfirmError] = useState('');
  const [resumeReady, setResumeReady] = useState(false);
  const [resumeLoading, setResumeLoading] = useState(false);
  const [resumeError, setResumeError] = useState('');
  const [resumeDraft, setResumeDraft] = useState(null);
  const [resumeToken, setResumeToken] = useState('');

  const [formFields, setFormFields] = useState({ form: [], customer: [], booking: [] });
  const [answers, setAnswers] = useState({});

  const [locations, setLocations] = useState([]);
  const [categories, setCategories] = useState([]);
  const [services, setServices] = useState([]);
  const [extras, setExtras] = useState([]);
  const [agents, setAgents] = useState([]);

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const sessionIdRef = useRef('');
  const trackedCloseRef = useRef(false);
  const trackedAbandonmentRef = useRef(false);
  const trackedConfirmedRef = useRef(false);
  const trackedSubmittedRef = useRef(false);
  const lastViewedStepRef = useRef('');

  const paymentsActive = bpSettings ? bpSettings.payments_enabled !== 0 : false;
  const steps = useMemo(() => {
    let nextSteps = baseSteps;

    if (!paymentsActive) {
      nextSteps = nextSteps.filter((s) => s.key !== 'payment');
    }
    if (skipSteps.length) {
      const skipSet = new Set(skipSteps);
      nextSteps = nextSteps.filter((s) => !skipSet.has(s.key));
    }

    return nextSteps;
  }, [baseSteps, paymentsActive, skipStepsSig]);
  const hasPaymentStep = useMemo(() => steps.some((s) => s.key === 'payment'), [steps]);
  const hasLocationStep = useMemo(() => steps.some((s) => s.key === 'location'), [steps]);
  const hasExtrasStep = useMemo(() => steps.some((s) => s.key === 'extras'), [steps]);
  const hasServiceStep = useMemo(() => steps.some((s) => s.key === 'service'), [steps]);
  const hasAgentStep = useMemo(() => steps.some((s) => s.key === 'agent'), [steps]);
  const shouldLoadLocations = hasLocationStep || forcedSkipSet.has('location') || preselect.locationId > 0;
  const showLocationSummary = hasLocationStep || !!locationId;
  const showExtrasSummary = hasExtrasStep || extraIds.length > 0;
  const step = steps[stepIndex] || steps[0] || DEFAULT_STEPS[0];
  const stepNumber = Math.min(stepIndex + 1, steps.length || 1);
  const progressPercent = steps.length ? Math.round((stepNumber / steps.length) * 100) : 0;
  const stepDescription = step?.subtitle || 'Please complete the steps to schedule your booking.';
  const enabledPaymentMethods = useMemo(
    () => normalizePaymentMethods(bpSettings?.payments_enabled_methods),
    [bpSettings?.payments_enabled_methods]
  );
  const enabledPaymentMethodsSig = enabledPaymentMethods.join('|');

  const themePrimary = widgetStyleOptions?.primaryColor || designConfig?.appearance?.primaryColor || '';
  const accent = (step?.accentOverride && String(step.accentOverride).trim() !== '')
    ? String(step.accentOverride).trim()
    : (themePrimary && String(themePrimary).trim() !== '' ? String(themePrimary).trim() : '');
  const overlayStyle = useMemo(() => {
    const style = {};
    if (widgetStyleOptions?.overlayColor) {
      style['--bp-overlay-bg'] = widgetStyleOptions.overlayColor;
    }
    return style;
  }, [widgetStyleOptions?.overlayColor]);
  const modalStyle = useMemo(() => {
    const style = {};
    if (accent) style['--bp-accent'] = accent;
    if (widgetStyleOptions?.backgroundColor) style['--bp-modal-bg'] = widgetStyleOptions.backgroundColor;
    if (widgetStyleOptions?.sideBackgroundColor) style['--bp-side-bg'] = widgetStyleOptions.sideBackgroundColor;
    if (widgetStyleOptions?.mainBackgroundColor) style['--bp-main-bg'] = widgetStyleOptions.mainBackgroundColor;
    if (widgetStyleOptions?.borderRadius) style['--bp-modal-radius'] = `${widgetStyleOptions.borderRadius}px`;
    if (widgetStyleOptions?.maxWidth) style['--bp-modal-max-width'] = `${widgetStyleOptions.maxWidth}px`;
    const borderStyle = String(designConfig?.appearance?.borderStyle || '').toLowerCase();
    if (borderStyle === 'square' && !widgetStyleOptions?.borderRadius) style['--bp-modal-radius'] = '0px';
    return style;
  }, [
    accent,
    designConfig?.appearance?.borderStyle,
    widgetStyleOptions?.backgroundColor,
    widgetStyleOptions?.sideBackgroundColor,
    widgetStyleOptions?.mainBackgroundColor,
    widgetStyleOptions?.borderRadius,
    widgetStyleOptions?.maxWidth,
  ]);

  const iconUrl = useMemo(() => {
    if (step?.imageUrl) return step.imageUrl;
    const file = String(step?.icon || step?.image || 'services.svg').trim();
    if (!file) return '';
    const isSvg = file.toLowerCase().endsWith('.svg');
    const base = isSvg ? (brand?.iconsBase || '') : (brand?.imagesBase || '');
    if (!base) return '';

    const url = base + file;
    const v = String(isSvg ? window.pointlybooking_FRONT?.iconsBuild : window.pointlybooking_FRONT?.imagesBuild || '').trim();
    if (!v) return url;
    const sep = url.includes('?') ? '&' : '?';
    return `${url}${sep}v=${encodeURIComponent(v)}`;
  }, [brand, step]);
  const helpTitle = designConfig?.texts?.helpTitle || 'Need help?';
  const helpPhone = designConfig?.texts?.helpPhone || designConfig?.layout?.helpPhone || brand?.helpPhone || '';
  const layoutShowSummary = resolveSummaryVisibility(designConfig?.layout?.showSummary);
  const showLeft = widgetStyleOptions?.showLeftPanel != null
    ? widgetStyleOptions.showLeftPanel
    : step?.showLeftPanel !== false;
  const showSummary = widgetStyleOptions?.showSummary != null
    ? widgetStyleOptions.showSummary
    : layoutShowSummary;
  const showHelp = step?.showHelpBox !== false;
  const modalGridClassName = showLeft && showSummary
    ? 'bp-modal-grid'
    : showLeft && !showSummary
      ? 'bp-modal-grid bp-modal-grid--left-main'
      : !showLeft && showSummary
        ? 'bp-modal-grid bp-modal-grid--main-summary'
        : 'bp-modal-grid bp-modal-grid--main-only';
  const defaultNextLabel = getDefaultNextLabel(step?.key, { requiresPayment: paymentsActive && hasPaymentStep });
  const globalNextLabel = designConfig?.texts?.nextLabel || defaultNextLabel;
  const globalBackLabel = designConfig?.texts?.backLabel || '<- Back';
  const labels = useMemo(() => ({
    next: (step?.buttonNextLabel != null && String(step.buttonNextLabel).trim() !== '') ? String(step.buttonNextLabel) : globalNextLabel,
    back: (step?.buttonBackLabel != null && String(step.buttonBackLabel).trim() !== '') ? String(step.buttonBackLabel) : globalBackLabel,
  }), [step?.buttonNextLabel, step?.buttonBackLabel, globalNextLabel, globalBackLabel]);
  const totalAmount = useMemo(() => {
    const svc = services.find((x) => String(x.id) === String(serviceId));
    const svcPrice = svc?.price != null ? Number(svc.price) : 0;
    const selectedExtras = extras.filter((extra) => (
      extraIds.includes(extra.id) || extraIds.includes(String(extra.id))
    ));
    const extrasPrice = selectedExtras.reduce((sum, item) => (
      sum + (item?.price != null ? Number(item.price) : 0)
    ), 0);
    return svcPrice + extrasPrice;
  }, [services, serviceId, extras, extraIds]);
  const summaryData = useMemo(() => buildSummaryData({
    settings: bpSettings,
    locations,
    categories,
    services,
    extras,
    agents,
    locationId,
    categoryIds,
    serviceId,
    extraIds,
    agentId,
  }), [bpSettings, locations, categories, services, extras, agents, locationId, categoryIds, serviceId, extraIds, agentId]);
  const mobileSummaryPreview = useMemo(() => {
    const parts = [];
    if (summaryData.svc?.name) {
      parts.push(summaryData.svc.name);
    }
    if (date && slot?.start_time) {
      parts.push(`${date} ${slot.start_time}`);
    } else if (date) {
      parts.push(date);
    }
    if (summaryData.totalLabel !== '-') {
      parts.push(summaryData.totalLabel);
    }
    return parts.length ? parts.join(' · ') : 'Tap to review your booking details';
  }, [summaryData, date, slot]);
  const mobileSummaryPreviewLabel = useMemo(() => {
    const parts = [];
    if (summaryData.svc?.name) parts.push(summaryData.svc.name);
    if (date && slot?.start_time) parts.push(`${date} ${slot.start_time}`);
    else if (date) parts.push(date);
    if (summaryData.totalLabel !== '-') parts.push(summaryData.totalLabel);
    return parts.length ? parts.join(' | ') : 'Tap to review your booking details';
  }, [summaryData.svc?.name, summaryData.totalLabel, date, slot?.start_time]);
  const selectedPaymentMethodLabel = (paymentsActive && hasPaymentStep)
    ? (PAYMENT_METHOD_LABELS[paymentMethod] || paymentMethod || '')
    : '';
  const confirmationSummary = useMemo(() => ({
    location: showLocationSummary ? (summaryData.loc?.name || '') : '',
    service: summaryData.svc?.name || '',
    agent: summaryData.ag?.name || '',
    date: date || '',
    time: slot?.start_time ? `${slot.start_time}${slot?.end_time ? ` - ${slot.end_time}` : ''}` : '',
    total: summaryData.totalLabel !== '-' ? summaryData.totalLabel : '',
    paymentMethod: selectedPaymentMethodLabel,
  }), [
    showLocationSummary,
    summaryData.loc?.name,
    summaryData.svc?.name,
    summaryData.ag?.name,
    summaryData.totalLabel,
    date,
    slot?.start_time,
    slot?.end_time,
    selectedPaymentMethodLabel,
  ]);

  const buildDraftSnapshot = () => {
    const filteredAnswers = {};
    Object.entries(answers || {}).forEach(([key, value]) => {
      if (String(key || '').startsWith('__')) return;
      filteredAnswers[key] = value;
    });

    const customerFirstName = String(
      answers?.['customer.first_name']
      || answers?.['customer.firstname']
      || answers?.['customer.given_name']
      || ''
    ).trim();
    const customerLastName = String(
      answers?.['customer.last_name']
      || answers?.['customer.lastname']
      || answers?.['customer.family_name']
      || ''
    ).trim();
    const customerName = `${customerFirstName} ${customerLastName}`.trim()
      || String(answers?.['customer.name'] || answers?.['customer.full_name'] || '').trim();
    const customerEmail = String(
      answers?.['customer.email']
      || answers?.['customer.customer_email']
      || answers?.['customer_email']
      || ''
    ).trim();
    const customerPhone = String(
      answers?.['customer.phone']
      || answers?.['customer.customer_phone']
      || answers?.['customer_phone']
      || ''
    ).trim();

    return {
      token: resumeToken || '',
      step_key: resumeStepKey(step?.key || ''),
      location_id: locationId || 0,
      category_ids: normalizeIdList(categoryIds),
      service_id: serviceId || 0,
      extra_ids: normalizeIdList(extraIds),
      agent_id: agentId || 0,
      date: date || '',
      slot: {
        start_time: slot?.start_time || slot?.start || '',
        end_time: slot?.end_time || slot?.end || '',
      },
      payment_method: paymentMethod || '',
      answers: filteredAnswers,
      customer_email: customerEmail,
      customer_phone: customerPhone,
      customer_name: customerName,
      customer_first_name: customerFirstName,
      customer_last_name: customerLastName,
      service_name: summaryData.svc?.name || '',
      agent_name: summaryData.ag?.name || '',
      total_label: summaryData.totalLabel !== '-' ? summaryData.totalLabel : '',
      total_value: totalAmount,
      source_url: typeof window !== 'undefined' ? window.location.href : '',
      savedAt: Date.now(),
    };
  };

  const trackFrontEvent = (eventKey, payload = {}, { preferBeacon = false } = {}) => {
    if (!eventKey) return Promise.resolve(null);
    const url = `${getRestBase()}/front/funnel/event`;
    const body = {
      event: eventKey,
      session_id: sessionIdRef.current || '',
      resume_token: resumeToken || '',
      source_url: typeof window !== 'undefined' ? window.location.href : '',
      ...payload,
    };
    const json = JSON.stringify(body);

    if (preferBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
      try {
        navigator.sendBeacon(url, new Blob([json], { type: 'application/json' }));
        return Promise.resolve({ queued: true });
      } catch (e) {
        // Fall through to fetch.
      }
    }

    return fetch(url, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: json,
      keepalive: preferBeacon,
    })
      .then((response) => response.json().catch(() => ({})))
      .catch(() => null);
  };

  const trackBookingConfirmed = (booking) => {
    if (trackedConfirmedRef.current) return;
    const status = String(booking?.status || '').toLowerCase();
    if (status !== 'confirmed') return;
    trackedConfirmedRef.current = true;
    clearWizardDraft();
    trackFrontEvent('booking_confirmed', {
      step_key: 'confirmation',
      payment_method: paymentMethod || '',
      booking_id: booking?.id || bookingId || 0,
      resume_token: resumeToken || '',
    });
  };

  const trackBookingSubmitted = (method, stepKey = step?.key || 'review') => {
    if (trackedSubmittedRef.current) return;
    trackedSubmittedRef.current = true;
    trackFrontEvent('booking_submitted', {
      step_key: stepKey,
      payment_method: method,
      resume_token: resumeToken || '',
    });
    clearWizardDraft();
  };

  const restoreDraftSnapshot = (draft) => {
    if (!draft || typeof draft !== 'object') return;
    setLocationId(draft.location_id || null);
    setCategoryIds(normalizeIdList(draft.category_ids));
    setServiceId(draft.service_id || null);
    setExtraIds(normalizeIdList(draft.extra_ids));
    setAgentId(draft.agent_id || null);
    setDate(draft.date || null);
    setSlot(draft.slot?.start_time ? {
      start_time: draft.slot.start_time,
      end_time: draft.slot?.end_time || '',
    } : null);
    setPaymentMethod(normalizePaymentMethod(draft.payment_method) || '');
    setAnswers(draft.answers && typeof draft.answers === 'object' ? draft.answers : {});
    setResumeToken(draft.token || draft.resume_token || resumeToken || '');
    setResumeDraft(null);
    setResumeError('');
    setResumeReady(true);
    setError('');
    clearWizardDraft();

    const resumeKey = resumeStepKey(draft.step_key || '');
    const idx = steps.findIndex((item) => item.key === resumeKey);
    setStepIndex(idx >= 0 ? idx : 0);

    trackFrontEvent('resume_restored', {
      step_key: resumeKey,
      payment_method: normalizePaymentMethod(draft.payment_method) || '',
      resume_token: draft.token || draft.resume_token || resumeToken || '',
    });
  };

  const discardResumeDraft = () => {
    const tokenToDiscard = resumeDraft?.token || resumeToken || '';
    clearWizardDraft();
    setResumeDraft(null);
    setResumeError('');
    setResumeReady(true);
    setResumeToken('');
    trackFrontEvent('draft_discarded', {
      step_key: resumeStepKey(resumeDraft?.step_key || step?.key || ''),
      resume_token: tokenToDiscard,
    });
  };

  const recordSessionClose = ({ reason = 'close', preferBeacon = false } = {}) => {
    if (!open) return;

    if (!trackedCloseRef.current) {
      trackedCloseRef.current = true;
      trackFrontEvent('modal_closed', {
        step_key: step?.key || '',
        payment_method: paymentMethod || '',
        reason,
      }, { preferBeacon });
    }

    if (trackedAbandonmentRef.current || step?.key === 'confirmation') return;

    if (resumeDraft && isDraftPayloadRestorable(resumeDraft)) {
      writeWizardDraft({
        ...resumeDraft,
        savedAt: Number(resumeDraft.savedAt || resumeDraft.saved_at_ts || 0) || Date.now(),
      });
      return;
    }

    const draft = buildDraftSnapshot();
    if (!isDraftPayloadRestorable(draft)) {
      clearWizardDraft();
      return;
    }

    writeWizardDraft(draft);
    if (!draft.customer_email) return;

    trackedAbandonmentRef.current = true;
    trackFrontEvent('abandoned_captured', {
      step_key: draft.step_key,
      payment_method: draft.payment_method || '',
      draft,
      reason,
      resume_token: draft.token || '',
    }, { preferBeacon });
  };

  closeRequestRef.current = () => {
    recordSessionClose({ reason: 'close', preferBeacon: false });
    onClose?.();
  };

  const handleClose = () => {
    closeRequestRef.current?.();
  };

  useEffect(() => {
    if (!open) return;
    restoreFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    document.body.classList.add('bp-modal-open');
    const frame = window.requestAnimationFrame(() => {
      closeButtonRef.current?.focus?.();
    });
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        event.preventDefault();
        closeRequestRef.current?.();
        return;
      }

      trapFocusWithin(event, modalRef.current, closeButtonRef.current);
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.body.classList.remove('bp-modal-open');
      document.removeEventListener('keydown', handleKeyDown);
      window.cancelAnimationFrame(frame);
      restoreFocusRef.current?.focus?.();
    };
  }, [open]);

  useEffect(() => {
    if (!open) return;
    sessionIdRef.current = createFrontSessionId();
    trackedCloseRef.current = false;
    trackedAbandonmentRef.current = false;
    trackedConfirmedRef.current = false;
    trackedSubmittedRef.current = false;
    lastViewedStepRef.current = '';
    setStepIndex(0);
    setMobileSummaryOpen(false);
    setError('');
    setLocationId(preselect.locationId || null);
    setCategoryIds(preselect.categoryIds || []);
    setServiceId(preselect.serviceId || null);
    setExtraIds([]);
    setAgentId(preselect.agentId || null);
    setDate(null);
    setSlot(null);
    setPaymentMethod('');
    setPaymentBookingId(0);
    setBookingId(null);
    setBookingKey('');
    setIsCreatingBooking(false);
    setCreateError(null);
    setReturnState({ mode: '', bookingId: 0, token: '', key: '', action: '' });
    setReturnLoading(false);
    setReturnError('');
    setConfirmData(null);
    setConfirmInfo(null);
    setConfirmLoading(false);
    setConfirmError('');
    setAnswers({});
    setResumeReady(false);
    setResumeLoading(false);
    setResumeError('');
    setResumeDraft(null);
    setResumeToken('');
    trackFrontEvent('session_started', {
      step_key: steps[0]?.key || 'service',
    });
  }, [open, preselect.locationId, preselect.serviceId, preselect.agentId, preselect.categoryIds]);

  useEffect(() => {
    if (!open) return undefined;

    if (hasPaymentReturnInUrl()) {
      setResumeReady(true);
      clearWizardDraft();
      return undefined;
    }

    let alive = true;
    const resumeQueryToken = hasResumeTokenInUrl();
    const localDraft = readWizardDraft();

    if (localDraft && !isDraftPayloadRestorable(localDraft)) {
      clearWizardDraft();
    }

    if (!resumeQueryToken) {
      if (localDraft && isDraftPayloadRestorable(localDraft)) {
        setResumeDraft(localDraft);
        setResumeToken(localDraft.token || localDraft.resume_token || '');
      }
      setResumeReady(true);
      return undefined;
    }

    setResumeLoading(true);
    setResumeError('');
    setResumeToken(resumeQueryToken);

    fetch(`${getRestBase()}/front/funnel/draft?token=${encodeURIComponent(resumeQueryToken)}`, {
      credentials: 'same-origin',
      headers: { 'X-WP-Nonce': window.pointlybooking_FRONT?.nonce || '' },
    })
      .then(async (response) => {
        const json = await response.json().catch(() => ({}));
        if (!response.ok || !json?.success || !json?.data) {
          throw new Error(json?.message || 'Resume link expired or unavailable.');
        }
        return json.data;
      })
      .then((draft) => {
        if (!alive) return;
        const normalizedDraft = {
          ...draft,
          savedAt: Number(draft?.saved_at_ts || 0) || Date.now(),
        };
        setResumeDraft(normalizedDraft);
        setResumeToken(normalizedDraft.token || resumeQueryToken);
        writeWizardDraft(normalizedDraft);
      })
      .catch((eventError) => {
        if (!alive) return;
        if (localDraft && isDraftPayloadRestorable(localDraft)) {
          setResumeDraft(localDraft);
          setResumeToken(localDraft.token || localDraft.resume_token || resumeQueryToken);
        }
        setResumeError(eventError?.message || 'Could not load the saved booking.');
      })
      .finally(() => {
        if (!alive) return;
        setResumeLoading(false);
        setResumeReady(true);
        clearResumeQuery();
      });

    return () => {
      alive = false;
    };
  }, [open]);

  useEffect(() => {
    if (!open || !showSummary) return;
    setMobileSummaryOpen(step.key === 'review' || step.key === 'payment' || step.key === 'confirmation');
  }, [open, showSummary, step.key]);

  useEffect(() => {
    if (!open) return;
    const frame = window.requestAnimationFrame(() => {
      if (mainRef.current) {
        if (typeof mainRef.current.scrollTo === 'function') {
          mainRef.current.scrollTo({ top: 0, left: 0, behavior: stepIndex > 0 ? 'smooth' : 'auto' });
        } else {
          mainRef.current.scrollTop = 0;
        }
      }
      headingRef.current?.focus?.({ preventScroll: true });
    });
    return () => window.cancelAnimationFrame(frame);
  }, [open, stepIndex]);

  useEffect(() => {
    if (!open) return;
    if (!hasServiceStep && !serviceId && services.length === 1) {
      setServiceId(services[0].id);
    }
  }, [open, hasServiceStep, serviceId, services]);

  useEffect(() => {
    if (!open) return;
    if (!hasAgentStep && !agentId && agents.length === 1) {
      setAgentId(agents[0].id);
    }
  }, [open, hasAgentStep, agentId, agents]);

  useEffect(() => {
    if (!open) return;
    if (!locations.length) return;

    const hasSelected = !!locationId && locations.some((item) => String(item.id) === String(locationId));
    if (hasSelected) return;

    if (forcedSkipSet.has('location')) {
      const preferredValid = preselect.locationId > 0
        ? locations.find((item) => String(item.id) === String(preselect.locationId))
        : null;
      const nextLocation = preferredValid?.id || locations[0]?.id || null;
      if (nextLocation && String(nextLocation) !== String(locationId || '')) {
        setLocationId(nextLocation);
      }
      return;
    }

    if (locationId) setLocationId(null);
  }, [open, locations, locationId, forcedSkipSet, preselect.locationId]);

  useEffect(() => {
    if (!open) return;
    if (!categories.length) return;

    const validSet = new Set(categories.map((item) => String(item.id)));
    const current = normalizeIdList(categoryIds);
    const validCurrent = current.filter((id) => validSet.has(String(id)));

    if (forcedSkipSet.has('category')) {
      if (validCurrent.length) {
        if (validCurrent.length !== current.length) {
          setCategoryIds(validCurrent);
        }
        return;
      }

      const validPreferred = (preselect.categoryIds || []).filter((id) => validSet.has(String(id)));
      const fallback = validPreferred.length ? validPreferred : (categories[0]?.id ? [categories[0].id] : []);
      if (fallback.length) {
        setCategoryIds(fallback);
      }
      return;
    }

    if (current.length && validCurrent.length !== current.length) {
      setCategoryIds(validCurrent);
    }
  }, [open, categories, categoryIds, forcedSkipSet, preselect.categoryIds]);

  useEffect(() => {
    if (!open) return;
    if (!services.length) return;

    const hasSelected = !!serviceId && services.some((item) => String(item.id) === String(serviceId));
    if (hasSelected) return;

    if (forcedSkipSet.has('service')) {
      const preferredValid = preselect.serviceId > 0
        ? services.find((item) => String(item.id) === String(preselect.serviceId))
        : null;
      const nextService = preferredValid?.id || services[0]?.id || null;
      if (nextService && String(nextService) !== String(serviceId || '')) {
        setServiceId(nextService);
      }
      return;
    }

    if (serviceId) setServiceId(null);
  }, [open, services, serviceId, forcedSkipSet, preselect.serviceId]);

  useEffect(() => {
    if (!open) return;
    if (!agents.length) return;

    const hasSelected = !!agentId && agents.some((item) => String(item.id) === String(agentId));
    if (hasSelected) return;

    if (forcedSkipSet.has('agent')) {
      const preferredValid = preselect.agentId > 0
        ? agents.find((item) => String(item.id) === String(preselect.agentId))
        : null;
      const nextAgent = preferredValid?.id || agents[0]?.id || null;
      if (nextAgent && String(nextAgent) !== String(agentId || '')) {
        setAgentId(nextAgent);
      }
      return;
    }

    if (agentId) setAgentId(null);
  }, [open, agents, agentId, forcedSkipSet, preselect.agentId]);

  useEffect(() => {
    if (!open) return;
    if (!paymentsActive) {
      setPaymentMethod('free');
      return;
    }
    const current = normalizePaymentMethod(paymentMethod);
    if (current && enabledPaymentMethods.includes(current)) return;

    const preferred = normalizePaymentMethod(bpSettings?.payments_default_method);
    if (preferred && enabledPaymentMethods.includes(preferred)) {
      setPaymentMethod(preferred);
      return;
    }

    if (enabledPaymentMethods[0]) {
      setPaymentMethod(enabledPaymentMethods[0]);
    }
  }, [open, bpSettings?.payments_default_method, paymentMethod, paymentsActive, enabledPaymentMethodsSig]);

  useEffect(() => {
    if (!open) return;

    const params = new URLSearchParams(window.location.search);
    const bpPayment = params.get('pointlybooking_payment');
    const token = params.get('token') || '';
    const bookingId = Number(params.get('booking_id') || 0);
    const key = params.get('key') || '';

    const stripeFlag = bpPayment === 'stripe_success' || bpPayment === 'stripe_cancel';
    const paypalFlag = bpPayment === 'paypal_return' || bpPayment === 'paypal_cancel';

    if (!bookingId) return;

    if (stripeFlag) {
      setReturnState({
        mode: 'stripe',
        bookingId,
        token: '',
        key,
        action: bpPayment === 'stripe_cancel' ? 'cancel' : 'success',
      });
    } else if (paypalFlag) {
      setReturnState({
        mode: 'paypal',
        bookingId,
        token,
        key,
        action: bpPayment === 'paypal_cancel' ? 'cancel' : 'success',
      });
    }
  }, [open]);

  useEffect(() => {
    if (!open) return;
    if (!returnState.bookingId || !returnState.mode) return;

    let alive = true;
    setReturnLoading(true);
    setReturnError('');

    (async () => {
      try {
        if (returnState.action === 'cancel') {
          await fetch(`${getRestBase()}/front/bookings/${returnState.bookingId}/payment-cancel`, {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ key: returnState.key }),
          });
          throw new Error('Payment cancelled.');
        }

        if (returnState.mode === 'paypal') {
          if (!returnState.token) {
            await fetch(`${getRestBase()}/front/bookings/${returnState.bookingId}/payment-cancel`, {
              method: 'POST',
              credentials: 'same-origin',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ key: returnState.key }),
            });
            throw new Error('Payment cancelled.');
          }
          await paypalCapture(returnState.token, returnState.bookingId, returnState.key);
        }

        const confirmed = await waitForConfirmed(returnState.bookingId, returnState.key);
        if (!alive) return;

        setBookingId(returnState.bookingId);
        setBookingKey(returnState.key);
        setPaymentBookingId(returnState.bookingId);
        setAnswers((a) => ({ ...a, __booking: { booking_id: returnState.bookingId } }));
        if (confirmed?.status === 'confirmed' && confirmed?.payment_status === 'paid') {
          setConfirmData({ booking: confirmed, paid: true, error: '' });
        } else {
          setConfirmData({
            booking: confirmed,
            paid: false,
            error: '',
            pending: true,
          });
        }
        goToStepKey('confirmation');
      } catch (e) {
        if (!alive) return;
        const message = e?.message || 'Payment failed';
        setReturnError(message);
        setBookingId(returnState.bookingId);
        setBookingKey(returnState.key);
        setPaymentBookingId(returnState.bookingId);
        setAnswers((a) => ({ ...a, __booking: { booking_id: returnState.bookingId } }));
        setConfirmData({ booking: { id: returnState.bookingId }, paid: false, error: message });
        goToStepKey('confirmation');
      } finally {
        if (!alive) return;
        setReturnLoading(false);
        clearPaymentQuery();
      }
    })();

    return () => {
      alive = false;
    };
  }, [open, returnState]);

  useEffect(() => {
    if (error === 'Select a payment method' && paymentMethod) {
      setError('');
    }
  }, [error, paymentMethod]);

  useEffect(() => {
    if (!open) return;
    (async () => {
      try {
        setLoading(true);
        const [locs, cats, fields] = await Promise.all([
          shouldLoadLocations ? fetchLocations() : Promise.resolve([]),
          fetchCategories(),
          fetchFormFields(),
        ]);
        setLocations(locs);
        setCategories(cats);
        setFormFields(fields);
      } catch (e) {
        setError(e?.message || 'Failed to load booking data.');
      } finally {
        setLoading(false);
      }
    })();
  }, [open, shouldLoadLocations]);

  useEffect(() => {
    if (!open) return;
    const keySig = steps.map((s) => s.key).join('|');
    if (!keySig) return;
    setStepIndex((current) => {
      if (current < 0) return 0;
      if (current >= steps.length) return Math.max(0, steps.length - 1);
      return current;
    });
  }, [open, steps]);

  useEffect(() => {
    setStepIndex((i) => {
      if (i < 0) return 0;
      if (i >= steps.length) return Math.max(0, steps.length - 1);
      return i;
    });
  }, [steps.length]);

  const bookingData = answers?.__booking || null;
  const currentStepKey = steps[stepIndex]?.key;

  useEffect(() => {
    if (!open || !currentStepKey) return;
    const signature = `${sessionIdRef.current}:${currentStepKey}`;
    if (lastViewedStepRef.current === signature) return;
    lastViewedStepRef.current = signature;
    trackFrontEvent('step_viewed', {
      step_key: currentStepKey,
      payment_method: currentStepKey === 'payment' ? paymentMethod || '' : '',
    });
  }, [open, currentStepKey, paymentMethod]);

  useEffect(() => {
    if (!open || currentStepKey !== 'payment') return;
    const method = normalizePaymentMethod(paymentMethod);
    if (!method) return;
    trackFrontEvent('payment_method_selected', {
      step_key: currentStepKey,
      payment_method: method,
    });
  }, [open, currentStepKey, paymentMethod]);

  useEffect(() => {
    if (!open || !resumeReady || resumeLoading || resumeDraft || currentStepKey === 'confirmation') return;
    const draft = buildDraftSnapshot();
    if (isDraftPayloadRestorable(draft)) {
      writeWizardDraft(draft);
    } else {
      clearWizardDraft();
    }
  }, [
    open,
    resumeReady,
    resumeLoading,
    resumeDraft,
    currentStepKey,
    locationId,
    categoryIds,
    serviceId,
    extraIds,
    agentId,
    date,
    slot,
    paymentMethod,
    answers,
    totalAmount,
    summaryData.svc?.name,
    summaryData.ag?.name,
    summaryData.totalLabel,
    step?.key,
    resumeToken,
  ]);

  useEffect(() => {
    if (!open) return undefined;
    const handlePageHide = () => {
      recordSessionClose({ reason: 'pagehide', preferBeacon: true });
    };
    window.addEventListener('pagehide', handlePageHide);
    return () => window.removeEventListener('pagehide', handlePageHide);
  }, [open, step?.key, paymentMethod, answers, date, slot, resumeToken, totalAmount, summaryData.svc?.name, summaryData.ag?.name, summaryData.totalLabel]);

  useEffect(() => {
    if (!open) return;
    if (currentStepKey !== 'confirmation') return;
    if (!bookingId) return;

    let alive = true;
    setConfirmLoading(true);
    setConfirmError('');

    fetchBookingStatus(bookingId, bookingKey)
      .then((b) => {
        if (!alive) return;
        setConfirmInfo(b);
        trackBookingConfirmed(b);
      })
      .catch((e) => alive && setConfirmError(e.message || 'Error'))
      .finally(() => alive && setConfirmLoading(false));

    return () => { alive = false; };
  }, [open, currentStepKey, bookingId, bookingKey]);

  useEffect(() => {
    if (!open) return;
    if (confirmData?.booking) {
      trackBookingConfirmed(confirmData.booking);
    }
    if (confirmInfo) {
      trackBookingConfirmed(confirmInfo);
    }
  }, [open, confirmData, confirmInfo]);

  useEffect(() => {
    if (!open) return;
    (async () => {
      try {
        if (step.key === 'service' || step.key === 'extras' || step.key === 'agent' || step.key === 'datetime' || step.key === 'review') {
          const svc = await fetchServices({ category_ids: categoryIds, location_id: locationId });
          setServices(svc);
        }
      } catch (e) {
        setServices([]);
      }
    })();
  }, [open, step.key, categoryIds, locationId]);

  useEffect(() => {
    if (!open) return;
    (async () => {
      try {
        if (!serviceId) return;
        const [ex, ag] = await Promise.all([
          hasExtrasStep ? fetchExtras({ service_id: serviceId }) : Promise.resolve([]),
          fetchAgents({ service_id: serviceId, location_id: locationId }),
        ]);
        setExtras(ex);
        setAgents(ag);
      } catch (e) {
        setExtras([]);
        setAgents([]);
      }
    })();
  }, [open, serviceId, locationId, hasExtrasStep]);

  function next() {
    setError('');
    if (step?.key === 'payment' && !paymentMethod) {
      setError('Select a payment method');
      return;
    }
    trackFrontEvent('step_completed', {
      step_key: step?.key || '',
      payment_method: step?.key === 'payment' ? paymentMethod || '' : '',
    });
    setStepIndex((i) => Math.min(i + 1, steps.length - 1));
  }

  function back() {
    setError('');
    trackFrontEvent('step_back', {
      step_key: step?.key || '',
      payment_method: step?.key === 'payment' ? paymentMethod || '' : '',
    });
    setStepIndex((i) => Math.max(i - 1, 0));
  }

  function goToStepKey(key) {
    const idx = steps.findIndex((s) => s.key === key);
    if (idx >= 0) setStepIndex(idx);
  }

  const buildPayload = () => {
    const customer_fields = {};
    const booking_fields = {};
    Object.entries(answers || {}).forEach(([k, v]) => {
      if (k.startsWith('customer.')) {
        customer_fields[k.slice(9)] = v;
      } else if (k.startsWith('booking.')) {
        booking_fields[k.slice(8)] = v;
      }
    });
    const settingsCurrency = bpSettings?.currency || window.pointlybooking_FRONT?.currency || 'USD';

    return {
      location_id: locationId,
      category_ids: categoryIds,
      service_id: serviceId,
      extra_ids: hasExtrasStep ? extraIds : [],
      agent_id: agentId,
      date,
      start_time: slot?.start_time || slot?.start || '',
      end_time: slot?.end_time || slot?.end || '',
      field_values: answers,
      customer_fields,
      booking_fields,
      extras: hasExtrasStep ? extraIds : [],
      total_price: totalAmount,
      currency: settingsCurrency,
    };
  };

  const createBookingIfNeeded = async () => {
    if (bookingId) return bookingId;

    setIsCreatingBooking(true);
    setCreateError(null);

    try {
      const payload = buildPayload();
      payload.payment_method = paymentsActive ? (paymentMethod || 'cash') : 'free';

      const res = await fetch(`${getRestBase()}/front/booking/create`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-WP-Nonce': window.pointlybooking_FRONT?.nonce || '',
        },
        credentials: 'same-origin',
        body: JSON.stringify(payload),
      });
      const j = await res.json().catch(() => ({}));
        if (!res.ok || !j?.success) {
          throw new Error(j?.message || 'Could not create booking');
        }

        setBookingId(j.booking_id);
        const createdKey = j.manage_key || extractManageKey(j.manage_url || '');
        if (createdKey) setBookingKey(createdKey);
        return j.booking_id;
    } catch (e) {
      setCreateError(e.message || 'Create booking failed');
      throw e;
    } finally {
      setIsCreatingBooking(false);
    }
  };

  async function submitBooking() {
    try {
      setLoading(true);
      setError('');

      const payload = buildPayload();

      const method = !paymentsActive
        ? 'free'
        : (paymentMethod || bpSettings?.payments_default_method || 'cash');
      payload.payment_method = method;

      if (method === 'woocommerce') {
        const res = await startWooCheckout(payload);
        if (!res?.checkout_url) {
          throw new Error('Checkout URL missing');
        }
        if (res?.manage_key) setBookingKey(res.manage_key);
        trackBookingSubmitted(method, step?.key || 'review');
        window.location.href = res.checkout_url;
        return;
      }

      if (method === 'stripe') {
        const res = await startStripeCheckout(payload);
        if (!res?.checkout_url) {
          throw new Error('Stripe checkout URL missing');
        }
        if (res?.manage_key) setBookingKey(res.manage_key);
        trackBookingSubmitted(method, step?.key || 'review');
        window.location.href = res.checkout_url;
        return;
      }

      if (method === 'paypal') {
        const res = await startPaypalCheckout(payload);
        if (!res?.approve_url) {
          throw new Error('PayPal approve URL missing');
        }
        if (res?.manage_key) setBookingKey(res.manage_key);
        trackBookingSubmitted(method, step?.key || 'review');
        window.location.href = res.approve_url;
        return;
      }

      if (method !== 'cash' && method !== 'free') {
        throw new Error('Unsupported payment method');
      }

      const res = await createBooking(payload);
      setBookingId(res?.booking_id || null);
      const manageKey = res?.manage_key || extractManageKey(res?.manage_url || '');
      if (manageKey) setBookingKey(manageKey);
      setPaymentBookingId(res?.booking_id || 0);
      trackBookingSubmitted(method, step?.key || 'review');
      setConfirmData({
        booking: res,
        paid: method === 'free',
        error: '',
      });
      setAnswers((a) => ({ ...a, __booking: res }));
      if (res?.status === 'confirmed') {
        trackBookingConfirmed(res);
      }
      const doneIdx = steps.findIndex((s) => s.key === 'confirmation');
      setStepIndex(doneIdx >= 0 ? doneIdx : steps.length - 1);
    } catch (e) {
      setError(e?.message || 'Could not submit booking.');
    } finally {
      setLoading(false);
    }
  }

  if (!open) return null;
  if (designLoading && !designConfig) {
    return <div className="bp-wizard-loading">Loading booking form...</div>;
  }
  if (designError && !designConfig) {
    return <div className="bp-wizard-loading">Wizard error: {designError}</div>;
  }
  if (!steps.length) {
    return <div className="bp-wizard-loading">Wizard config invalid (no steps)</div>;
  }

  return (
    <div
      className="bp-modal-overlay"
      role="dialog"
      aria-modal="true"
      aria-labelledby={dialogTitleId}
      style={overlayStyle}
      onMouseDown={(event) => {
        if (event.target === event.currentTarget) handleClose();
      }}
    >
      <div
        ref={modalRef}
        className="bp-modal"
        style={modalStyle}
        onMouseDown={(event) => event.stopPropagation()}
        onClickCapture={(event) => {
          if (event.target instanceof Element && event.target.closest('.bp-modal-close')) {
            event.preventDefault();
            event.stopPropagation();
            handleClose();
          }
        }}
      >
        <button ref={closeButtonRef} type="button" className="bp-modal-close" onClick={handleClose} aria-label="Close">×</button>

        <div className={modalGridClassName}>
          {showLeft ? (
            <aside className="bp-side">
              <div className="bp-side-step">Step {stepNumber} of {steps.length}</div>
              <div className="bp-side-icon">
                {iconUrl ? <img src={iconUrl} alt="" /> : null}
              </div>
              <h3 className="bp-side-title">{step.title}</h3>
              <p className="bp-side-desc">
                {stepDescription}
              </p>
              <div className="bp-side-progress" aria-hidden="true">
                <span style={{ width: `${progressPercent}%` }} />
              </div>

              {showHelp ? (
                <div className="bp-side-help">
                  <div>{helpTitle}</div>
                  <div className="bp-help-phone">{helpPhone}</div>
                </div>
              ) : null}
            </aside>
          ) : null}

          <main ref={mainRef} className="bp-main">
            <div className="bp-main-head">
              <div className="bp-main-head__copy">
                <div className="bp-main-eyebrow">Step {stepNumber} of {steps.length}</div>
                <h2 ref={headingRef} id={dialogTitleId} tabIndex="-1">{step.title}</h2>
                <p className="bp-main-head__desc">{stepDescription}</p>
              </div>
              <div className="bp-progress-card" aria-hidden="true">
                <div className="bp-progress-card__meta">
                  <span>{progressPercent}% complete</span>
                  <span>{steps.length - stepNumber} step{steps.length - stepNumber === 1 ? '' : 's'} left</span>
                </div>
                <div className="bp-progress-bar">
                  <span style={{ width: `${progressPercent}%` }} />
                </div>
                <div className="bp-step-dots">
                  {steps.map((s, idx) => (
                    <span key={s.key} className={idx === stepIndex ? 'bp-dot active' : idx < stepIndex ? 'bp-dot done' : 'bp-dot'} />
                  ))}
                </div>
              </div>
            </div>

            <div className="bp-mobile-context">
              <div className={iconUrl ? 'bp-mobile-intro' : 'bp-mobile-intro bp-mobile-intro--textOnly'}>
                {iconUrl ? (
                  <div className="bp-mobile-intro__icon">
                    <img src={iconUrl} alt="" />
                  </div>
                ) : null}
                <div className="bp-mobile-intro__body">
                  <span className="bp-mobile-intro__eyebrow">Step {stepNumber} of {steps.length}</span>
                  <p className="bp-mobile-intro__desc">
                    {stepDescription}
                  </p>
                  {showHelp ? (
                    <div className="bp-mobile-intro__help">
                      <span>{helpTitle}</span>
                      <strong>{helpPhone || 'Support available during booking hours.'}</strong>
                    </div>
                  ) : null}
                </div>
              </div>

              {showSummary ? (
                <section className={mobileSummaryOpen ? 'bp-mobile-summary is-open' : 'bp-mobile-summary'}>
                  <button
                    type="button"
                    className="bp-mobile-summary__toggle"
                    onClick={() => setMobileSummaryOpen((isOpen) => !isOpen)}
                    aria-expanded={mobileSummaryOpen}
                    aria-controls={mobileSummaryPanelId}
                  >
                    <span className="bp-mobile-summary__copy">
                      <span className="bp-mobile-summary__eyebrow">Booking summary</span>
                      <span className="bp-mobile-summary__preview">{mobileSummaryPreviewLabel || mobileSummaryPreview}</span>
                    </span>
                    <span className="bp-mobile-summary__state">{mobileSummaryOpen ? 'Hide' : 'Show'}</span>
                  </button>
                  <div id={mobileSummaryPanelId} className="bp-mobile-summary__panel" aria-hidden={!mobileSummaryOpen}>
                    <div className="bp-mobile-summary__panelInner">
                      <div className="bp-summary-box">
                        <SummaryBlock
                          settings={bpSettings}
                          locations={locations}
                          categories={categories}
                          services={services}
                          extras={extras}
                          agents={agents}
                          locationId={locationId}
                          categoryIds={categoryIds}
                          serviceId={serviceId}
                          extraIds={extraIds}
                          agentId={agentId}
                          date={date}
                          slot={slot}
                          showLocation={showLocationSummary}
                          showExtras={showExtrasSummary}
                        />
                      </div>
                    </div>
                  </div>
                </section>
              ) : null}
            </div>

            {resumeLoading ? <div className="bp-loading">Loading your saved booking...</div> : null}
            {resumeError ? <div className="bp-error">{resumeError}</div> : null}
            {resumeDraft ? (
              <section className="bp-resume-card">
                <div className="bp-resume-card__copy">
                  <div className="bp-resume-card__eyebrow">Saved booking found</div>
                  <h3>Pick up where you left off</h3>
                  <p>
                    {resumeDraft.service_name || 'Your booking draft'} was last saved {formatDraftTime(resumeDraft.savedAt || resumeDraft.saved_at_ts)}.
                    {resumeDraft.date ? ` Planned for ${resumeDraft.date}` : ''}
                    {resumeDraft.slot?.start_time ? ` at ${resumeDraft.slot.start_time}` : ''}.
                  </p>
                </div>
                <div className="bp-resume-card__actions">
                  <button type="button" className="bp-btn bp-btn-primary" onClick={() => restoreDraftSnapshot(resumeDraft)}>
                    Resume booking
                  </button>
                  <button type="button" className="bp-btn bp-btn-light" onClick={discardResumeDraft}>
                    Start fresh
                  </button>
                </div>
              </section>
            ) : null}
            {error ? <div className="bp-error">{error}</div> : null}
            {loading ? <div className="bp-loading">Loading booking data...</div> : null}

            {step.key === 'location' && (
              <StepLocation
                locations={locations}
                value={locationId}
                onChange={setLocationId}
                onNext={next}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'category' && (
              <StepCategory
                categories={categories}
                value={categoryIds}
                onChange={setCategoryIds}
                onBack={back}
                onNext={next}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'service' && (
              <StepService
                services={services}
                value={serviceId}
                onChange={setServiceId}
                onBack={back}
                onNext={next}
                settings={bpSettings}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'extras' && (
              <StepExtras
                extras={extras}
                value={extraIds}
                onChange={setExtraIds}
                onBack={back}
                onNext={next}
                settings={bpSettings}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'agent' && (
              <StepAgent
                agents={agents}
                value={agentId}
                onChange={setAgentId}
                onBack={back}
                onNext={next}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'datetime' && (
              <StepDateTime
                locationId={locationId}
                serviceId={serviceId}
                agentId={agentId}
                serviceDurationMin={services.find((s) => String(s.id) === String(serviceId))?.duration || 30}
                valueDate={date}
                valueSlot={slot}
                onChangeDate={(v) => {
                  setDate(v);
                  setSlot(null);
                }}
                onChangeSlot={setSlot}
                onBack={back}
                onNext={next}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'customer' && (
              <StepCustomer
                formFields={formFields}
                answers={answers}
                onChange={setAnswers}
                onBack={back}
                onNext={next}
                onError={setError}
                layout={designConfig?.fieldsLayout}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'payment' && (
              <StepPayment
                bookingId={bookingId}
                bookingKey={bookingKey}
                paymentMethod={paymentMethod}
                paymentMethodLabel={selectedPaymentMethodLabel}
                enabledMethods={enabledPaymentMethods}
                imagesBase={brand?.imagesBase || ''}
                totalLabel={formatMoney(totalAmount, bpSettings)}
                onChangePaymentMethod={setPaymentMethod}
                onPrepareStripePayment={createBookingIfNeeded}
                onSubmitBooking={submitBooking}
                onStripeConfirmStart={() => trackBookingSubmitted('stripe', 'payment')}
                onBack={() => {
                  trackFrontEvent('step_back', {
                    step_key: 'payment',
                    payment_method: paymentMethod || '',
                  });
                  goToStepKey('review');
                }}
                onPaid={() => goToStepKey('confirmation')}
              />
            )}

            {step.key === 'review' && (
              <StepReview
                locationId={locationId}
                categoryIds={categoryIds}
                serviceId={serviceId}
                extraIds={extraIds}
                agentId={agentId}
                date={date}
                slot={slot}
                locations={locations}
                categories={categories}
                services={services}
                extras={extras}
                agents={agents}
                formFields={formFields}
                answers={answers}
                bookingId={bookingId}
                showLocation={showLocationSummary}
                showExtras={showExtrasSummary}
                showPayment={paymentsActive && hasPaymentStep}
                hasPayment={paymentsActive && hasPaymentStep}
                paymentMethodLabel={selectedPaymentMethodLabel || '-'}
                totalLabel={formatMoney(totalAmount, bpSettings)}
                onBack={back}
                onNext={async () => {
                  trackFrontEvent('step_completed', {
                    step_key: 'review',
                    payment_method: paymentMethod || '',
                  });
                  if (paymentsActive && hasPaymentStep) {
                    goToStepKey('payment');
                    return;
                  }
                  await submitBooking();
                }}
                isCreatingBooking={isCreatingBooking}
                createError={createError}
                loading={loading}
                backLabel={labels.back}
                nextLabel={labels.next}
              />
            )}

            {step.key === 'confirmation' && (
              <StepConfirmation
                bookingId={bookingId}
                confirmInfo={confirmInfo}
                loading={confirmLoading}
                error={confirmError}
                summary={confirmationSummary}
                onClose={handleClose}
                onRetry={() => goToStepKey('payment')}
              />
            )}
          </main>

          {showSummary ? (
            <aside className="bp-summary">
              <div className="bp-summary-title">Summary</div>
              <div className="bp-summary-box">
                <SummaryBlock
                  settings={bpSettings}
                  locations={locations}
                  categories={categories}
                  services={services}
                  extras={extras}
                  agents={agents}
                  locationId={locationId}
                  categoryIds={categoryIds}
                  serviceId={serviceId}
                  extraIds={extraIds}
                  agentId={agentId}
                  date={date}
                  slot={slot}
                  showLocation={showLocationSummary}
                  showExtras={showExtrasSummary}
                />
              </div>
            </aside>
          ) : null}
        </div>
      </div>
    </div>
  );
}

function buildSummaryData(props) {
  const {
    settings,
    locations, categories, services, extras, agents,
    locationId, categoryIds, serviceId, extraIds, agentId,
  } = props;

  const loc = locations.find((x) => String(x.id) === String(locationId));
  const svc = services.find((x) => String(x.id) === String(serviceId));
  const ag = agents.find((x) => String(x.id) === String(agentId));
  const ex = extras.filter((x) => extraIds.includes(x.id) || extraIds.includes(String(x.id)));
  const cats = categories.filter((x) => categoryIds.includes(x.id) || categoryIds.includes(String(x.id)));
  const svcPrice = svc?.price != null ? Number(svc.price) : 0;
  const extrasPrice = ex.reduce((sum, item) => sum + (item?.price != null ? Number(item.price) : 0), 0);
  const totalPrice = svcPrice + extrasPrice;

  return {
    loc,
    svc,
    ag,
    ex,
    cats,
    svcPrice,
    extrasPrice,
    totalPrice,
    servicePriceLabel: svc?.price != null ? formatMoney(svcPrice, settings) : '-',
    extrasPriceLabel: ex.length ? formatMoney(extrasPrice, settings) : '-',
    totalLabel: svc?.price != null || ex.length ? formatMoney(totalPrice, settings) : '-',
  };
}

function SummaryBlock(props) {
  const {
    settings,
    locations, categories, services, extras, agents,
    locationId, categoryIds, serviceId, extraIds, agentId, date, slot,
    showLocation = true,
    showExtras = true,
  } = props;

  const {
    loc,
    svc,
    ag,
    ex,
    cats,
    servicePriceLabel,
    extrasPriceLabel,
    totalLabel,
  } = buildSummaryData({
    settings,
    locations,
    categories,
    services,
    extras,
    agents,
    locationId,
    categoryIds,
    serviceId,
    extraIds,
    agentId,
  });

  return (
    <div className="bp-summary-items">
      {showLocation ? (
        <div className="bp-summary-row">
          <div className="k">Location</div>
          <div className="v">{loc?.name || '-'}</div>
        </div>
      ) : null}
      <div className="bp-summary-row">
        <div className="k">Category</div>
        <div className="v">{cats.length ? cats.map((c) => c.name).join(', ') : '-'}</div>
      </div>
      <div className="bp-summary-row">
        <div className="k">Service</div>
        <div className="v">{svc?.name || '-'}</div>
      </div>
      <div className="bp-summary-row">
        <div className="k">Service Price</div>
        <div className="v">{servicePriceLabel}</div>
      </div>
      <div className="bp-summary-row">
        <div className="k">Agent</div>
        <div className="v">{ag?.name || '-'}</div>
      </div>
      <div className="bp-summary-row">
        <div className="k">Date</div>
        <div className="v">{date || '-'}</div>
      </div>
      <div className="bp-summary-row">
        <div className="k">Time</div>
        <div className="v">{slot?.start_time ? `${slot.start_time} - ${slot.end_time}` : '-'}</div>
      </div>
      {showExtras ? (
        <>
          <div className="bp-summary-row">
            <div className="k">Extras</div>
            <div className="v">{ex.length ? ex.map((e) => e.name).join(', ') : '-'}</div>
          </div>
          <div className="bp-summary-row">
            <div className="k">Extras Price</div>
            <div className="v">{extrasPriceLabel}</div>
          </div>
        </>
      ) : null}
      <div className="bp-summary-row bp-summary-row--total">
        <div className="k">Total</div>
        <div className="v">{svc?.price != null || (showExtras && ex.length) ? totalLabel : '-'}</div>
      </div>
    </div>
  );
}
