{"version":3,"sources":["../../src/hooks/useScheduling.ts"],"names":[],"mappings":";;;AAmFO,SAAS,aAAA,CAAc,IAAA,GAA6B,EAAC,EAAwB;AAClF,EAAA,MAAM,QAAA,GAAW,OAAA;AAAA,IACf,MAAM,IAAA,CAAK,OAAA,IAAW,yBAAA,EAA0B;AAAA,IAChD,CAAC,KAAK,OAAO;AAAA,GACf;AAEA,EAAA,MAAM,CAAC,OAAO,QAAQ,CAAA,GAAI,SAA0B,EAAE,MAAA,EAAQ,QAAQ,CAAA;AACtE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,QAAA,CAA8B,EAAE,CAAA;AAChE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAA+B,EAAE,CAAA;AACnE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAA2B,EAAE,CAAA;AACvD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAKtD,EAAA,MAAM,QAAA,GAAW,OAAwB,KAAK,CAAA;AAC9C,EAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AAGnB,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA;AAC9C,EAAA,cAAA,CAAe,UAAU,IAAA,CAAK,WAAA;AAG9B,EAAA,MAAM,aAAA,GAAgB,OAAO,KAAK,CAAA;AAElC,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,CAAC,GAAA,EAAc,QAAA,KAAqB;AAC7D,IAAA,IAAI,eAAe,qBAAA,EAAuB;AACxC,MAAA,QAAA,CAAS,IAAI,OAAO,CAAA;AAAA,IACtB,CAAA,MAAA,IAAW,eAAe,KAAA,EAAO;AAC/B,MAAA,QAAA,CAAS,IAAI,OAAO,CAAA;AAAA,IACtB,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,IACnB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,YAAA,GAAe,YAAY,YAAY;AAC3C,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,aAAA,CAAgC,QAAA,EAAU,WAAW,CAAA;AACxE,MAAA,WAAA,CAAY,IAAA,EAAM,QAAA,IAAY,EAAE,CAAA;AAAA,IAClC,SAAS,GAAA,EAAK;AACZ,MAAA,MAAA,CAAO,KAAK,0BAA0B,CAAA;AAAA,IACxC,CAAA,SAAE;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IAClB;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,MAAM,CAAC,CAAA;AAGrB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAK,eAAA,EAAiB;AAC1B,IAAA,YAAA,EAAa;AAAA,EACf,CAAA,EAAG,CAAC,YAAA,EAAc,IAAA,CAAK,eAAe,CAAC,CAAA;AAEvC,EAAA,MAAM,uBAAA,GAA0B,WAAA;AAAA,IAC9B,OAAO,SAAA,KAAqD;AAC1D,MAAA,MAAM,IAAA,GAAO,MAAM,aAAA,CAAiC,QAAA,EAAU,YAAA,EAAc;AAAA,QAC1E,UAAA,EAAY;AAAA,OACb,CAAA;AACD,MAAA,MAAM,IAAA,GAAO,IAAA,EAAM,SAAA,IAAa,EAAC;AACjC,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,MAAM,sBAAA,GAAyB,CAAC,CAAC,IAAA,CAAK,sBAAA;AACtC,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,OAAO,OAAA,KAA+B;AACpC,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,QAAA,CAAS,EAAE,CAAA;AACX,MAAA,QAAA,CAAS,EAAE,MAAA,EAAQ,kBAAA,EAAoB,OAAA,EAAS,CAAA;AAChD,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,GAAO,MAAM,uBAAA,CAAwB,OAAA,CAAQ,EAAE,CAAA;AAIrD,QAAA,IAAI,sBAAA,IAA0B,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG;AAC/C,UAAA,QAAA,CAAS;AAAA,YACP,MAAA,EAAQ,mBAAA;AAAA,YACR,OAAA;AAAA,YACA,QAAA,EAAU,KAAK,CAAC;AAAA,WACjB,CAAA;AAAA,QACH;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,MACnD,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,uBAAA,EAAyB,MAAA,EAAQ,sBAAsB;AAAA,GAC1D;AAEA,EAAA,MAAM,YAAA,GAAe,WAAA;AAAA,IACnB,OAAO,QAAA,KAAwC;AAC7C,MAAA,IAAI,QAAA,KAAa,IAAA,IAAQ,CAAC,IAAA,CAAK,gBAAA,EAAkB;AAC/C,QAAA;AAAA,MACF;AACA,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,QAAA,CAAS,EAAE,CAAA;AACX,MAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AAEjB,QAAA,IAAI,KAAK,MAAA,KAAW,MAAA,IAAU,IAAA,CAAK,MAAA,KAAW,SAAS,OAAO,IAAA;AAC9D,QAAA,MAAM,OAAA,GACJ,SAAA,IAAa,IAAA,GAAO,IAAA,CAAK,OAAA,GAAW,IAAA;AACtC,QAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,QAAA,OAAO,EAAE,MAAA,EAAQ,mBAAA,EAAqB,OAAA,EAAS,QAAA,EAAS;AAAA,MAC1D,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,KAAK,gBAAgB;AAAA,GACxB;AAEA,EAAA,MAAM,gBAAA,GAAmB,WAAA;AAAA,IACvB,OAAO,MAAc,EAAA,KAAe;AAClC,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,IAAI;AAIF,QAAA,MAAM,UAAU,QAAA,CAAS,OAAA;AACzB,QAAA,MAAM,YACJ,SAAA,IAAa,OAAA,IAAW,QAAQ,OAAA,GAC5B,OAAA,CAAQ,QAAQ,EAAA,GAChB,KAAA,CAAA;AACN,QAAA,MAAM,aACJ,UAAA,IAAc,OAAA,IAAW,QAAQ,QAAA,GAC7B,OAAA,CAAQ,SAAS,EAAA,GACjB,KAAA,CAAA;AACN,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,QAAA,CAAS,6CAA6C,CAAA;AACtD,UAAA;AAAA,QACF;AACA,QAAA,MAAM,OAAO,MAAM,aAAA;AAAA,UACjB,QAAA;AAAA,UACA,eAAA;AAAA,UACA;AAAA,YACE,UAAA,EAAY,SAAA;AAAA,YACZ,aAAa,UAAA,IAAc,KAAA,CAAA;AAAA,YAC3B,IAAA;AAAA,YACA;AAAA;AACF,SACF;AACA,QAAA,QAAA,CAAS,IAAA,EAAM,KAAA,IAAS,EAAE,CAAA;AAAA,MAC5B,SAAS,GAAA,EAAK;AACZ,QAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA;AAAA,MAC5C,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,MAAM;AAAA,GACnB;AAEA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,CAAC,IAAA,KAAyB;AACrD,IAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,MAAA,IACE,IAAA,CAAK,WAAW,mBAAA,IAChB,IAAA,CAAK,WAAW,eAAA,IAChB,IAAA,CAAK,WAAW,gBAAA,EAChB;AACA,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,eAAA;AAAA,QACR,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,UAAU,IAAA,CAAK,QAAA;AAAA,QACf;AAAA,OACF;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAa,WAAA,CAAY,CAAC,OAAA,KAAsC;AACpE,IAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,MAAA,IAAI,IAAA,CAAK,MAAA,KAAW,eAAA,IAAmB,IAAA,CAAK,WAAW,gBAAA,EAAkB;AACvE,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,gBAAA;AAAA,QACR,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,MAAM,IAAA,CAAK,IAAA;AAAA,QACX;AAAA,OACF;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAAS,YAAY,YAAY;AACrC,IAAA,IAAI,cAAc,OAAA,EAAS;AAO3B,IAAA,MAAM,UAAU,QAAA,CAAS,OAAA;AACzB,IAAA,IAAI,OAAA,CAAQ,WAAW,gBAAA,EAAkB;AACvC,MAAA,QAAA,CAAS,yCAAyC,CAAA;AAClD,MAAA;AAAA,IACF;AACA,IAAA,MAAM,YAAA,GAAe,OAAA;AACrB,IAAA,QAAA,CAAS;AAAA,MACP,MAAA,EAAQ,YAAA;AAAA,MACR,SAAS,YAAA,CAAa,OAAA;AAAA,MACtB,UAAU,YAAA,CAAa,QAAA;AAAA,MACvB,MAAM,YAAA,CAAa,IAAA;AAAA,MACnB,SAAS,YAAA,CAAa;AAAA,KACvB,CAAA;AAED,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,YAAA;AACjB,MAAA,MAAM,IAAA,GAAO,MAAM,cAAA,CAAgC,QAAA,EAAU,WAAA,EAAa;AAAA,QACxE,UAAA,EAAY,SAAS,OAAA,CAAQ,EAAA;AAAA,QAC7B,WAAA,EAAa,QAAA,CAAS,QAAA,EAAU,EAAA,IAAM,IAAA;AAAA,QACtC,SAAA,EAAW,SAAS,IAAA,CAAK,KAAA;AAAA,QACzB,OAAA,EAAS,SAAS,IAAA,CAAK,GAAA;AAAA,QACvB,aAAA,EAAe,SAAS,OAAA,CAAQ,aAAA;AAAA,QAChC,cAAA,EAAgB,SAAS,OAAA,CAAQ,cAAA;AAAA,QACjC,cAAA,EAAgB,QAAA,CAAS,OAAA,CAAQ,cAAA,IAAkB,IAAA;AAAA,QACnD,KAAA,EAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,IAAS;AAAA,OAClC,CAAA;AAED,MAAA,MAAM,OAAA,GAAsC;AAAA,QAC1C,EAAA,EAAI,IAAA,EAAM,UAAA,IAAc,IAAA,EAAM,EAAA,IAAM,EAAA;AAAA,QACpC,gBAAA,EAAkB,MAAM,gBAAA,IAAoB,IAAA;AAAA,QAC5C,mBAAA,EAAqB,MAAM,mBAAA,IAAuB,IAAA;AAAA,QAClD,eAAe,IAAA,EAAM;AAAA,OACvB;AAEA,MAAA,QAAA,CAAS;AAAA,QACP,MAAA,EAAQ,WAAA;AAAA,QACR,SAAS,QAAA,CAAS,OAAA;AAAA,QAClB,UAAU,QAAA,CAAS,QAAA;AAAA,QACnB,MAAM,QAAA,CAAS,IAAA;AAAA,QACf,SAAS,QAAA,CAAS,OAAA;AAAA,QAClB;AAAA,OACD,CAAA;AACD,MAAA,cAAA,CAAe,UAAU,OAAO,CAAA;AAAA,IAClC,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,OAAA,GACJ,eAAe,qBAAA,GAAwB,GAAA,CAAI,UACzC,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAC3B,iBAAA;AACJ,MAAA,QAAA,CAAS,OAAO,CAAA;AAGhB,MAAA,QAAA;AAAA,QAAS,CAAC,IAAA,KACR,IAAA,CAAK,MAAA,KAAW,YAAA,GACZ;AAAA,UACE,MAAA,EAAQ,gBAAA;AAAA,UACR,SAAS,IAAA,CAAK,OAAA;AAAA,UACd,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,SAAS,IAAA,CAAK;AAAA,SAChB,GACA;AAAA,OACN;AAAA,IACF,CAAA,SAAE;AACA,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AAAA,IAC1B;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,QAAA,CAAS,EAAE,MAAA,EAAQ,MAAA,EAAQ,CAAA;AAC3B,IAAA,YAAA,CAAa,EAAE,CAAA;AACf,IAAA,QAAA,CAAS,EAAE,CAAA;AACX,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EAGf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,IAAA,GAAO,YAAY,MAAM;AAC7B,IAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,MAAA,QAAQ,KAAK,MAAA;AAAQ,QACnB,KAAK,kBAAA;AACH,UAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAAA,QAC1B,KAAK,mBAAA;AACH,UAAA,OAAO,EAAE,MAAA,EAAQ,kBAAA,EAAoB,OAAA,EAAS,KAAK,OAAA,EAAQ;AAAA,QAC7D,KAAK,eAAA;AACH,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,mBAAA;AAAA,YACR,SAAS,IAAA,CAAK,OAAA;AAAA,YACd,UAAU,IAAA,CAAK;AAAA,WACjB;AAAA,QACF,KAAK,gBAAA;AACH,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,eAAA;AAAA,YACR,SAAS,IAAA,CAAK,OAAA;AAAA,YACd,UAAU,IAAA,CAAK,QAAA;AAAA,YACf,MAAM,IAAA,CAAK;AAAA,WACb;AAAA,QACF;AACE,UAAA,OAAO,IAAA;AAAA;AACX,IACF,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,gBAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF","file":"useScheduling.mjs","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  resolveSchedulingBasePath,\n  schedulingGet,\n  schedulingPost,\n  SchedulingClientError,\n} from '../lib/schedulingClient';\nimport type {\n  SchedulingBookingDetails,\n  SchedulingConfirmedBooking,\n  SchedulingResource,\n  SchedulingService,\n  SchedulingSlot,\n  SchedulingState,\n} from '../types/scheduling';\n\nexport interface UseSchedulingOptions {\n  /** Override the API base path. Default: auto-detect via resolveSchedulingBasePath(). */\n  apiBase?: string;\n  /** Called once a booking is confirmed (use this to redirect, fire analytics, etc.). */\n  onConfirmed?: (booking: SchedulingConfirmedBooking) => void;\n  /**\n   * If true, the hook will not auto-load services on mount — the consumer\n   * calls `loadServices()` explicitly. Useful if the booking flow is gated\n   * behind a CTA and the initial fetch shouldn't happen until the user opens it.\n   */\n  skipInitialLoad?: boolean;\n  /**\n   * Allow the customer to pick \"Any available\" instead of a specific resource.\n   * Defaults to false — most templates want explicit resource selection. When\n   * true, `pickResource(null)` is permitted and availability fans out across\n   * all eligible resources.\n   */\n  allowAnyResource?: boolean;\n  /**\n   * When the resources list for a chosen service has exactly one entry, skip\n   * the resource picker and go straight to the date/time step. Defaults to\n   * false — surprising-skip is bad UX, and the picker still adds a useful\n   * confirmation step (\"yes, this stylist\") even when there's only one.\n   * Templates that genuinely want the skip can opt back in.\n   */\n  autoSkipSingleResource?: boolean;\n}\n\nexport interface UseSchedulingResult {\n  basePath: string;\n  state: SchedulingState;\n\n  services: SchedulingService[];\n  resources: SchedulingResource[];\n  slots: SchedulingSlot[];\n\n  /** Catalog or availability fetch in flight. Submission has its own state status. */\n  loading: boolean;\n  /** Last error string (catalog/availability/submit). Cleared on next successful op. */\n  error: string | null;\n\n  loadServices: () => Promise<void>;\n  pickService: (service: SchedulingService) => Promise<void>;\n  pickResource: (resource: SchedulingResource | null) => Promise<void>;\n  loadAvailability: (from: string, to: string) => Promise<void>;\n  pickSlot: (slot: SchedulingSlot) => void;\n  setDetails: (details: SchedulingBookingDetails) => void;\n  submit: () => Promise<void>;\n\n  /** Reset to idle, preserving the cached `services` list so the picker stays warm. */\n  reset: () => void;\n\n  /** Step back one stage of the flow without nuking caches. */\n  back: () => void;\n}\n\ninterface ServicesResponse { services?: SchedulingService[] }\ninterface ResourcesResponse { resources?: SchedulingResource[] }\ninterface AvailabilityResponse { slots?: SchedulingSlot[]; googleSkipped?: boolean }\ninterface BookingResponse {\n  booking_id?: string;\n  id?: string;\n  reschedule_token?: string;\n  calendar_event_link?: string | null;\n  googleSkipped?: boolean;\n}\n\nexport function useScheduling(opts: UseSchedulingOptions = {}): UseSchedulingResult {\n  const basePath = useMemo(\n    () => opts.apiBase ?? resolveSchedulingBasePath(),\n    [opts.apiBase],\n  );\n\n  const [state, setState] = useState<SchedulingState>({ status: 'idle' });\n  const [services, setServices] = useState<SchedulingService[]>([]);\n  const [resources, setResources] = useState<SchedulingResource[]>([]);\n  const [slots, setSlots] = useState<SchedulingSlot[]>([]);\n  const [loading, setLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  // Track the latest committed state in a ref so async ops (loadAvailability,\n  // submit) can read it synchronously without depending on useState's queued\n  // updater pattern, which doesn't run until the next render.\n  const stateRef = useRef<SchedulingState>(state);\n  stateRef.current = state;\n\n  // Snapshot the latest onConfirmed so callers don't have to memoize it.\n  const onConfirmedRef = useRef(opts.onConfirmed);\n  onConfirmedRef.current = opts.onConfirmed;\n\n  // Single in-flight submit guard — re-entrant submits are no-ops, not races.\n  const submittingRef = useRef(false);\n\n  const setErr = useCallback((err: unknown, fallback: string) => {\n    if (err instanceof SchedulingClientError) {\n      setError(err.message);\n    } else if (err instanceof Error) {\n      setError(err.message);\n    } else {\n      setError(fallback);\n    }\n  }, []);\n\n  const loadServices = useCallback(async () => {\n    setLoading(true);\n    setError(null);\n    try {\n      const data = await schedulingGet<ServicesResponse>(basePath, '/services');\n      setServices(data?.services ?? []);\n    } catch (err) {\n      setErr(err, 'Failed to load services.');\n    } finally {\n      setLoading(false);\n    }\n  }, [basePath, setErr]);\n\n  // Initial fetch — populates the picker without the consumer wiring it.\n  useEffect(() => {\n    if (opts.skipInitialLoad) return;\n    loadServices();\n  }, [loadServices, opts.skipInitialLoad]);\n\n  const loadResourcesForService = useCallback(\n    async (serviceId: string): Promise<SchedulingResource[]> => {\n      const data = await schedulingGet<ResourcesResponse>(basePath, '/resources', {\n        service_id: serviceId,\n      });\n      const list = data?.resources ?? [];\n      setResources(list);\n      return list;\n    },\n    [basePath],\n  );\n\n  const autoSkipSingleResource = !!opts.autoSkipSingleResource;\n  const pickService = useCallback(\n    async (service: SchedulingService) => {\n      setError(null);\n      setSlots([]);\n      setState({ status: 'service_selected', service });\n      setLoading(true);\n      try {\n        const list = await loadResourcesForService(service.id);\n        // Optional UX shortcut: if there's only one viable resource, skip the\n        // resource picker. Off by default — the explicit confirmation step is\n        // usually clearer than a surprising auto-advance.\n        if (autoSkipSingleResource && list.length === 1) {\n          setState({\n            status: 'resource_selected',\n            service,\n            resource: list[0],\n          });\n        }\n      } catch (err) {\n        setErr(err, 'Failed to load available providers.');\n      } finally {\n        setLoading(false);\n      }\n    },\n    [loadResourcesForService, setErr, autoSkipSingleResource],\n  );\n\n  const pickResource = useCallback(\n    async (resource: SchedulingResource | null) => {\n      if (resource === null && !opts.allowAnyResource) {\n        return;\n      }\n      setError(null);\n      setSlots([]);\n      setState((prev) => {\n        // Resource pick is only meaningful once a service has been chosen.\n        if (prev.status === 'idle' || prev.status === 'error') return prev;\n        const service =\n          'service' in prev ? prev.service : (null as unknown as SchedulingService);\n        if (!service) return prev;\n        return { status: 'resource_selected', service, resource };\n      });\n    },\n    [opts.allowAnyResource],\n  );\n\n  const loadAvailability = useCallback(\n    async (from: string, to: string) => {\n      setError(null);\n      setLoading(true);\n      try {\n        // Read the latest committed state from the ref. setState updaters\n        // run during the next render, so the older \"let serviceId; setState\n        // ((prev) => { serviceId = prev... })\" pattern always saw undefined.\n        const current = stateRef.current;\n        const serviceId =\n          'service' in current && current.service\n            ? current.service.id\n            : undefined;\n        const resourceId =\n          'resource' in current && current.resource\n            ? current.resource.id\n            : undefined;\n        if (!serviceId) {\n          setError('Pick a service before loading availability.');\n          return;\n        }\n        const data = await schedulingGet<AvailabilityResponse>(\n          basePath,\n          '/availability',\n          {\n            service_id: serviceId,\n            resource_id: resourceId ?? undefined,\n            from,\n            to,\n          },\n        );\n        setSlots(data?.slots ?? []);\n      } catch (err) {\n        setErr(err, 'Failed to load availability.');\n      } finally {\n        setLoading(false);\n      }\n    },\n    [basePath, setErr],\n  );\n\n  const pickSlot = useCallback((slot: SchedulingSlot) => {\n    setState((prev) => {\n      if (\n        prev.status !== 'resource_selected' &&\n        prev.status !== 'slot_selected' &&\n        prev.status !== 'details_filled'\n      ) {\n        return prev;\n      }\n      return {\n        status: 'slot_selected',\n        service: prev.service,\n        resource: prev.resource,\n        slot,\n      };\n    });\n  }, []);\n\n  const setDetails = useCallback((details: SchedulingBookingDetails) => {\n    setState((prev) => {\n      if (prev.status !== 'slot_selected' && prev.status !== 'details_filled') {\n        return prev;\n      }\n      return {\n        status: 'details_filled',\n        service: prev.service,\n        resource: prev.resource,\n        slot: prev.slot,\n        details,\n      };\n    });\n  }, []);\n\n  const submit = useCallback(async () => {\n    if (submittingRef.current) return;\n\n    // Read latest committed state from the ref. The previous\n    // \"setState((prev) => { payloadState = prev; ... })\" pattern captured an\n    // updater into React's queue, so payloadState was always null until the\n    // next render — submit silently no-op'd. Use the ref + an explicit\n    // setState for the status transition.\n    const current = stateRef.current;\n    if (current.status !== 'details_filled') {\n      setError('Fill in your details before submitting.');\n      return;\n    }\n    const payloadState = current;\n    setState({\n      status: 'submitting',\n      service: payloadState.service,\n      resource: payloadState.resource,\n      slot: payloadState.slot,\n      details: payloadState.details,\n    });\n\n    submittingRef.current = true;\n    setError(null);\n    try {\n      const captured = payloadState as Extract<SchedulingState, { status: 'details_filled' }>;\n      const data = await schedulingPost<BookingResponse>(basePath, '/bookings', {\n        service_id: captured.service.id,\n        resource_id: captured.resource?.id ?? null,\n        starts_at: captured.slot.start,\n        ends_at: captured.slot.end,\n        customer_name: captured.details.customer_name,\n        customer_email: captured.details.customer_email,\n        customer_phone: captured.details.customer_phone ?? null,\n        notes: captured.details.notes ?? null,\n      });\n\n      const booking: SchedulingConfirmedBooking = {\n        id: data?.booking_id ?? data?.id ?? '',\n        reschedule_token: data?.reschedule_token ?? null,\n        calendar_event_link: data?.calendar_event_link ?? null,\n        googleSkipped: data?.googleSkipped,\n      };\n\n      setState({\n        status: 'confirmed',\n        service: captured.service,\n        resource: captured.resource,\n        slot: captured.slot,\n        details: captured.details,\n        booking,\n      });\n      onConfirmedRef.current?.(booking);\n    } catch (err) {\n      const message =\n        err instanceof SchedulingClientError ? err.message\n        : err instanceof Error ? err.message\n        : 'Booking failed.';\n      setError(message);\n      // Step back to details_filled so the consumer can retry without\n      // re-entering anything.\n      setState((prev) =>\n        prev.status === 'submitting'\n          ? {\n              status: 'details_filled',\n              service: prev.service,\n              resource: prev.resource,\n              slot: prev.slot,\n              details: prev.details,\n            }\n          : prev,\n      );\n    } finally {\n      submittingRef.current = false;\n    }\n  }, [basePath]);\n\n  const reset = useCallback(() => {\n    setState({ status: 'idle' });\n    setResources([]);\n    setSlots([]);\n    setError(null);\n    // Intentionally keep `services` so the next opening of the flow doesn't\n    // re-fetch the catalog.\n  }, []);\n\n  const back = useCallback(() => {\n    setState((prev) => {\n      switch (prev.status) {\n        case 'service_selected':\n          return { status: 'idle' };\n        case 'resource_selected':\n          return { status: 'service_selected', service: prev.service };\n        case 'slot_selected':\n          return {\n            status: 'resource_selected',\n            service: prev.service,\n            resource: prev.resource,\n          };\n        case 'details_filled':\n          return {\n            status: 'slot_selected',\n            service: prev.service,\n            resource: prev.resource,\n            slot: prev.slot,\n          };\n        default:\n          return prev;\n      }\n    });\n  }, []);\n\n  return {\n    basePath,\n    state,\n    services,\n    resources,\n    slots,\n    loading,\n    error,\n    loadServices,\n    pickService,\n    pickResource,\n    loadAvailability,\n    pickSlot,\n    setDetails,\n    submit,\n    reset,\n    back,\n  };\n}\n"]}