"use client";
// ../blocks/src/system/runtime/utils/api-url.ts
function resolveMultiEnvDashboardUrl() {
  const env = process.env.RIVERBANK_ENV;
  if (!env) return null;
  const envKey = `RIVERBANK_${env.toUpperCase()}_DASHBOARD_URL`;
  const dashboardUrl = process.env[envKey];
  if (dashboardUrl) {
    return dashboardUrl.replace(/\/$/, "");
  }
  return null;
}
function getCmsApiUrl() {
  const multiEnvDashboard = resolveMultiEnvDashboardUrl();
  if (multiEnvDashboard) {
    return `${multiEnvDashboard}/api`;
  }
  const builderApiUrl = process.env.NEXT_PUBLIC_BUILDER_API_URL;
  if (builderApiUrl) {
    return builderApiUrl.replace(/\/$/, "");
  }
  const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
  if (dashboardUrl) {
    const base = dashboardUrl.replace(/\/$/, "");
    return `${base}/api`;
  }
  const legacyApiUrl = process.env.NEXT_PUBLIC_CMS_API_URL;
  if (legacyApiUrl) {
    return legacyApiUrl.replace(/\/$/, "");
  }
  throw new Error(
    "Dashboard URL not configured. Either:\n  1. Set RIVERBANK_ENV with RIVERBANK_LOCAL_DASHBOARD_URL / RIVERBANK_REMOTE_DASHBOARD_URL\n  2. Set NEXT_PUBLIC_BUILDER_API_URL (SDK sites)\n  3. Set NEXT_PUBLIC_DASHBOARD_URL (frontend app)"
  );
}

// ../blocks/src/system/runtime/nodes/booking-form.client.tsx
import React5, { useState as useState7 } from "react";

// ../blocks/src/system/runtime/components/multi-step/MultiStepForm.tsx
import { cloneElement, isValidElement } from "react";

// ../blocks/src/system/runtime/components/multi-step/MultiStepContext.tsx
import { createContext, useContext } from "react";
var MultiStepContext = createContext(null);
function useMultiStepContext() {
  const context = useContext(MultiStepContext);
  if (!context) {
    throw new Error(
      "useMultiStepContext must be used within a MultiStepForm component. Make sure your component is wrapped in <MultiStepForm>."
    );
  }
  return context;
}
function useMultiStepData() {
  const { data, updateData } = useMultiStepContext();
  return { data, updateData };
}

// ../blocks/src/system/runtime/components/multi-step/useMultiStep.ts
import { useState, useCallback, useMemo, useEffect } from "react";
function useMultiStep({
  steps,
  initialData = {},
  onStepChange,
  onDataChange,
  onComplete,
  persistToUrl = false
}) {
  const [data, setData] = useState(initialData);
  const [currentStepIndex, setCurrentStepIndex] = useState(0);
  const [errors, setErrors] = useState({});
  const [isValidating, setIsValidating] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const visibleSteps = useMemo(() => {
    return steps.filter((step) => {
      if (!step.condition) return true;
      return step.condition(data);
    });
  }, [steps, data]);
  const currentStep = visibleSteps[currentStepIndex];
  const currentStepId = currentStep?.id || "";
  useEffect(() => {
    if (persistToUrl && currentStep) {
      const url = new URL(window.location.href);
      url.searchParams.set("step", currentStep.id);
      window.history.replaceState({}, "", url.toString());
    }
  }, [currentStepIndex, currentStep, persistToUrl]);
  useEffect(() => {
    if (persistToUrl) {
      const url = new URL(window.location.href);
      const stepId = url.searchParams.get("step");
      if (stepId) {
        const stepIndex = visibleSteps.findIndex((s) => s.id === stepId);
        if (stepIndex !== -1) {
          setCurrentStepIndex(stepIndex);
        }
      }
    }
  }, []);
  useEffect(() => {
    if (onStepChange && currentStep) {
      onStepChange(currentStep.id, currentStepIndex);
    }
  }, [currentStepIndex, currentStep, onStepChange]);
  const updateData = useCallback(
    (updates) => {
      setData((prev) => {
        const newData = { ...prev, ...updates };
        onDataChange?.(newData);
        return newData;
      });
      setErrors({});
    },
    [onDataChange]
  );
  const validateCurrentStep = useCallback(async () => {
    if (!currentStep?.validate) return true;
    setIsValidating(true);
    setErrors({});
    try {
      const result = await currentStep.validate(data);
      if (result.valid) {
        return true;
      } else {
        setErrors(result.errors);
        return false;
      }
    } catch (error) {
      console.error("Step validation error:", error);
      setErrors({ _form: "Validation failed. Please try again." });
      return false;
    } finally {
      setIsValidating(false);
    }
  }, [currentStep, data]);
  const goToNext = useCallback(async () => {
    const isValid = await validateCurrentStep();
    if (!isValid) return;
    if (currentStepIndex === visibleSteps.length - 1) {
      if (onComplete) {
        setIsSubmitting(true);
        try {
          await onComplete(data);
        } catch (error) {
          console.error("Form submission error:", error);
          setErrors({ _form: "Submission failed. Please try again." });
        } finally {
          setIsSubmitting(false);
        }
      }
      return;
    }
    setCurrentStepIndex((prev) => Math.min(prev + 1, visibleSteps.length - 1));
    setErrors({});
  }, [currentStepIndex, visibleSteps.length, validateCurrentStep, onComplete, data]);
  const goToPrevious = useCallback(() => {
    setCurrentStepIndex((prev) => Math.max(prev - 1, 0));
    setErrors({});
  }, []);
  const goToStep = useCallback(
    (stepIndex) => {
      if (stepIndex >= 0 && stepIndex < visibleSteps.length) {
        setCurrentStepIndex(stepIndex);
        setErrors({});
      }
    },
    [visibleSteps.length]
  );
  const goToStepById = useCallback(
    (stepId) => {
      const stepIndex = visibleSteps.findIndex((s) => s.id === stepId);
      if (stepIndex !== -1) {
        goToStep(stepIndex);
      }
    },
    [visibleSteps, goToStep]
  );
  const reset = useCallback(() => {
    setData(initialData);
    setCurrentStepIndex(0);
    setErrors({});
    setIsValidating(false);
    setIsSubmitting(false);
  }, [initialData]);
  const canGoNext = !isValidating && !isSubmitting;
  const canGoBack = currentStepIndex > 0 && !isValidating && !isSubmitting;
  const isFirstStep = currentStepIndex === 0;
  const isLastStep = currentStepIndex === visibleSteps.length - 1;
  return {
    currentStepIndex,
    currentStepId,
    visibleSteps,
    data,
    errors,
    isValidating,
    isSubmitting,
    goToNext,
    goToPrevious,
    goToStep,
    goToStepById,
    updateData,
    canGoNext,
    canGoBack,
    isFirstStep,
    isLastStep,
    reset
  };
}

// ../blocks/src/system/runtime/components/multi-step/MultiStepForm.tsx
import { jsx, jsxs } from "react/jsx-runtime";
function MultiStepForm({
  steps,
  initialData,
  onComplete,
  onStepChange,
  onDataChange,
  progressStyle = "dots",
  className = "",
  persistToUrl = false,
  allowSkip = false
}) {
  const context = useMultiStep({
    steps,
    initialData,
    onStepChange,
    onDataChange,
    onComplete,
    persistToUrl
  });
  const {
    visibleSteps,
    currentStepIndex,
    goToNext,
    goToPrevious,
    goToStep,
    canGoNext,
    canGoBack,
    isFirstStep,
    isLastStep,
    errors,
    isValidating,
    isSubmitting
  } = context;
  const currentStep = visibleSteps[currentStepIndex];
  if (!currentStep) {
    return /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No steps available" }) });
  }
  const stepComponent = isValidElement(currentStep.component) ? cloneElement(currentStep.component, {
    // Step components can access context via hooks, but we also pass key props
    stepId: currentStep.id,
    stepIndex: currentStepIndex
  }) : currentStep.component;
  const showBackButton = !isFirstStep && !currentStep.hideBack && canGoBack;
  const showNextButton = !currentStep.isTerminal;
  const nextButtonLabel = isLastStep ? currentStep.nextLabel || "Complete" : currentStep.nextLabel || "Continue";
  const backButtonLabel = currentStep.backLabel || "Back";
  return /* @__PURE__ */ jsx(MultiStepContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: `multi-step-form ${className}`, children: [
    progressStyle !== "none" && /* @__PURE__ */ jsx("div", { className: "mb-8", children: /* @__PURE__ */ jsx(
      ProgressIndicator,
      {
        steps: visibleSteps,
        currentIndex: currentStepIndex,
        style: progressStyle,
        onStepClick: allowSkip ? goToStep : void 0
      }
    ) }),
    /* @__PURE__ */ jsx("div", { className: "step-content", children: stepComponent }),
    Object.keys(errors).length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 alert alert-error", children: [
      errors._form && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: errors._form }),
      Object.entries(errors).filter(([key]) => key !== "_form").map(([key, message]) => /* @__PURE__ */ jsx("p", { className: "text-sm", children: message }, key))
    ] }),
    (showBackButton || showNextButton) && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mt-8", children: [
      showBackButton && /* @__PURE__ */ jsx(
        "button",
        {
          type: "button",
          onClick: goToPrevious,
          disabled: !canGoBack,
          className: "outline btn-md",
          children: backButtonLabel
        }
      ),
      showNextButton && /* @__PURE__ */ jsxs(
        "button",
        {
          type: "button",
          onClick: goToNext,
          disabled: !canGoNext,
          className: "primary btn-md flex-1",
          children: [
            isValidating && /* @__PURE__ */ jsx("span", { className: "inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-current" }),
            isSubmitting && /* @__PURE__ */ jsx("span", { className: "inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-current" }),
            !isValidating && !isSubmitting && nextButtonLabel,
            isValidating && "Validating...",
            isSubmitting && "Submitting..."
          ]
        }
      )
    ] })
  ] }) });
}
function ProgressIndicator({
  steps,
  currentIndex,
  style,
  onStepClick
}) {
  if (style === "dots") {
    return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center gap-2", children: steps.map((step, index) => {
      const isActive = index === currentIndex;
      const isCompleted = index < currentIndex;
      const isClickable = onStepClick && index <= currentIndex;
      return /* @__PURE__ */ jsx(
        "button",
        {
          type: "button",
          onClick: isClickable ? () => onStepClick(index) : void 0,
          disabled: !isClickable,
          className: `
                step-dot transition-all
                ${isActive ? "step-dot-active" : ""}
                ${isCompleted ? "step-dot-complete" : ""}
                ${isClickable ? "cursor-pointer hover:scale-125" : "cursor-default"}
              `,
          "aria-label": step.title
        },
        step.id
      );
    }) });
  }
  if (style === "steps") {
    return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: steps.map((step, index) => {
      const isActive = index === currentIndex;
      const isCompleted = index < currentIndex;
      const isClickable = onStepClick && index <= currentIndex;
      const isLast = index === steps.length - 1;
      return /* @__PURE__ */ jsxs("div", { className: "flex items-center flex-1", children: [
        /* @__PURE__ */ jsxs(
          "button",
          {
            type: "button",
            onClick: isClickable ? () => onStepClick(index) : void 0,
            disabled: !isClickable,
            className: `
                  flex items-center gap-2
                  ${isClickable ? "cursor-pointer" : "cursor-default"}
                `,
            children: [
              /* @__PURE__ */ jsx(
                "div",
                {
                  className: `
                    step-circle transition-colors
                    ${isActive ? "step-circle-active" : ""}
                    ${isCompleted ? "step-circle-complete" : ""}
                  `,
                  children: isCompleted ? "\u2713" : index + 1
                }
              ),
              /* @__PURE__ */ jsx(
                "span",
                {
                  className: `
                    text-sm hidden sm:inline
                    ${isActive ? "font-medium" : ""}
                    ${isCompleted || !isActive ? "status-muted" : ""}
                  `,
                  children: step.title
                }
              )
            ]
          }
        ),
        !isLast && /* @__PURE__ */ jsx("div", { className: "step-connector mx-2 hidden sm:block" })
      ] }, step.id);
    }) });
  }
  const progress = (currentIndex + 1) / steps.length * 100;
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
    /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm status-muted", children: [
      /* @__PURE__ */ jsxs("span", { children: [
        "Step ",
        currentIndex + 1,
        " of ",
        steps.length
      ] }),
      /* @__PURE__ */ jsxs("span", { children: [
        Math.round(progress),
        "%"
      ] })
    ] }),
    /* @__PURE__ */ jsx("div", { className: "progress-bar", children: /* @__PURE__ */ jsx(
      "div",
      {
        className: "progress-fill",
        style: { width: `${progress}%` }
      }
    ) })
  ] });
}

// ../blocks/src/system/runtime/components/booking/SuccessMessage.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
var SuccessMessage = ({ message, className }) => {
  return /* @__PURE__ */ jsx2(
    "div",
    {
      className: `alert alert-success text-sm ${className ?? ""}`,
      role: "alert",
      "aria-live": "polite",
      children: message
    }
  );
};

// ../blocks/src/system/runtime/hooks/useBookingSteps.ts
import React4, { useMemo as useMemo3 } from "react";

// ../blocks/src/system/runtime/components/booking/ServiceResourceSelector.tsx
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
function ServiceResourceSelector({
  siteId,
  services,
  preSelectedServiceId,
  preSelectedResourceId,
  onSelect,
  onBack,
  heading = "Select Service",
  description = "Choose the service you'd like to book"
}) {
  const [selectedServiceId, setSelectedServiceId] = useState2(
    preSelectedServiceId
  );
  const [selectedResourceId, setSelectedResourceId] = useState2(
    preSelectedResourceId
  );
  const [resources, setResources] = useState2([]);
  const [isLoadingResources, setIsLoadingResources] = useState2(false);
  const [resourceError, setResourceError] = useState2(null);
  const showServiceSelect = !preSelectedServiceId && services.length > 1;
  const showResourceSelect = resources.length > 1 && !preSelectedResourceId;
  const handleServiceChange = useCallback2(async (serviceId) => {
    setSelectedServiceId(serviceId);
    setSelectedResourceId(void 0);
    setResourceError(null);
    try {
      setIsLoadingResources(true);
      const apiUrl = getCmsApiUrl();
      const response = await fetch(
        `${apiUrl}/sites/${siteId}/bookings/resources/reference?serviceId=${serviceId}`
      );
      if (!response.ok) {
        throw new Error("Failed to load resources");
      }
      const data = await response.json();
      const loadedResources = data.options?.map((opt) => ({
        id: opt.id,
        displayName: opt.label
      })) || [];
      setResources(loadedResources);
    } catch (err) {
      console.error("Failed to load resources:", err);
      setResourceError("Failed to load practitioners. Please try again.");
      setResources([]);
    } finally {
      setIsLoadingResources(false);
    }
  }, [siteId]);
  const handleContinue = () => {
    if (selectedServiceId) {
      onSelect({
        serviceId: selectedServiceId,
        resourceId: selectedResourceId === "__any__" ? void 0 : selectedResourceId
      });
    }
  };
  const canContinue = selectedServiceId && !isLoadingResources;
  useEffect2(() => {
    if (services.length === 1 && !selectedServiceId) {
      handleServiceChange(services[0].id);
    }
  }, [services, selectedServiceId, handleServiceChange]);
  return /* @__PURE__ */ jsxs2("div", { className: "space-y-6 max-w-2xl mx-auto", children: [
    /* @__PURE__ */ jsxs2("div", { children: [
      /* @__PURE__ */ jsx3("h2", { className: "text-2xl font-bold", children: heading }),
      description && /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground mt-2", children: description })
    ] }),
    showServiceSelect && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
      /* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Service" }),
      /* @__PURE__ */ jsx3("div", { className: "grid gap-3", children: services.map((service) => /* @__PURE__ */ jsx3(
        "button",
        {
          type: "button",
          onClick: () => handleServiceChange(service.id),
          className: `
                  relative p-4 rounded-lg border-2 text-left transition-all
                  ${selectedServiceId === service.id ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
                `,
          children: /* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between", children: [
            /* @__PURE__ */ jsxs2("div", { children: [
              /* @__PURE__ */ jsx3("div", { className: "font-medium", children: service.title }),
              service.description && /* @__PURE__ */ jsx3("div", { className: "text-sm text-muted-foreground mt-1", children: service.description }),
              service.durationMinutes && /* @__PURE__ */ jsxs2("div", { className: "text-sm text-muted-foreground mt-1", children: [
                service.durationMinutes,
                " minutes"
              ] })
            ] }),
            selectedServiceId === service.id && /* @__PURE__ */ jsx3("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ jsx3(
              "svg",
              {
                className: "w-3 h-3 text-primary-foreground",
                fill: "none",
                strokeLinecap: "round",
                strokeLinejoin: "round",
                strokeWidth: "2",
                viewBox: "0 0 24 24",
                stroke: "currentColor",
                children: /* @__PURE__ */ jsx3("path", { d: "M5 13l4 4L19 7" })
              }
            ) })
          ] })
        },
        service.id
      )) })
    ] }),
    showResourceSelect && selectedServiceId && !isLoadingResources && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
      /* @__PURE__ */ jsx3("label", { className: "text-sm font-medium", children: "Practitioner (Optional)" }),
      /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: 'Choose a specific practitioner or select "Any Available"' }),
      /* @__PURE__ */ jsxs2("div", { className: "grid gap-3", children: [
        /* @__PURE__ */ jsx3(
          "button",
          {
            type: "button",
            onClick: () => setSelectedResourceId("__any__"),
            className: `
                relative p-4 rounded-lg border-2 text-left transition-all
                ${selectedResourceId === "__any__" ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
              `,
            children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
              /* @__PURE__ */ jsx3("div", { className: "font-medium", children: "Any Available" }),
              selectedResourceId === "__any__" && /* @__PURE__ */ jsx3("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ jsx3(
                "svg",
                {
                  className: "w-3 h-3 text-primary-foreground",
                  fill: "none",
                  strokeLinecap: "round",
                  strokeLinejoin: "round",
                  strokeWidth: "2",
                  viewBox: "0 0 24 24",
                  stroke: "currentColor",
                  children: /* @__PURE__ */ jsx3("path", { d: "M5 13l4 4L19 7" })
                }
              ) })
            ] })
          }
        ),
        resources.map((resource) => /* @__PURE__ */ jsx3(
          "button",
          {
            type: "button",
            onClick: () => setSelectedResourceId(resource.id),
            className: `
                  relative p-4 rounded-lg border-2 text-left transition-all
                  ${selectedResourceId === resource.id ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
                `,
            children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
              /* @__PURE__ */ jsx3("div", { className: "font-medium", children: resource.displayName }),
              selectedResourceId === resource.id && /* @__PURE__ */ jsx3("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ jsx3(
                "svg",
                {
                  className: "w-3 h-3 text-primary-foreground",
                  fill: "none",
                  strokeLinecap: "round",
                  strokeLinejoin: "round",
                  strokeWidth: "2",
                  viewBox: "0 0 24 24",
                  stroke: "currentColor",
                  children: /* @__PURE__ */ jsx3("path", { d: "M5 13l4 4L19 7" })
                }
              ) })
            ] })
          },
          resource.id
        ))
      ] })
    ] }),
    isLoadingResources && /* @__PURE__ */ jsxs2("div", { className: "text-center py-8", children: [
      /* @__PURE__ */ jsx3("div", { className: "inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }),
      /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground mt-2", children: "Loading practitioners..." })
    ] }),
    resourceError && /* @__PURE__ */ jsx3("div", { className: "alert alert-error text-sm", children: resourceError }),
    /* @__PURE__ */ jsxs2("div", { className: "flex gap-3", children: [
      onBack && /* @__PURE__ */ jsx3(
        "button",
        {
          type: "button",
          onClick: onBack,
          className: "px-6 py-2 rounded-lg border border-border hover:bg-muted transition-colors",
          children: "Back"
        }
      ),
      /* @__PURE__ */ jsx3(
        "button",
        {
          type: "button",
          onClick: handleContinue,
          disabled: !canContinue,
          className: "flex-1 px-6 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
          children: "Continue to Date Selection"
        }
      )
    ] })
  ] });
}

// ../blocks/src/system/runtime/components/booking/ServiceSelectionStep.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
function ServiceSelectionStep({
  siteId,
  services
}) {
  const { updateData } = useMultiStepData();
  return /* @__PURE__ */ jsx4(
    ServiceResourceSelector,
    {
      siteId,
      services,
      onSelect: ({ serviceId, resourceId }) => {
        updateData({ serviceId, resourceId });
      }
    }
  );
}

// ../blocks/src/system/runtime/components/booking/DateTimeSelectionStep.tsx
import { useEffect as useEffect5, useMemo as useMemo2 } from "react";

// ../blocks/src/utils/date-formatting.ts
var BOOKING_FETCH_CHUNK_DAYS = 30;
function formatDate(dateStr) {
  const date = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
  return date.toLocaleDateString(void 0, {
    weekday: "long",
    year: "numeric",
    month: "long",
    day: "numeric"
  });
}
function formatTime(isoString) {
  const date = new Date(isoString);
  return date.toLocaleTimeString(void 0, {
    hour: "numeric",
    minute: "2-digit",
    timeZoneName: "short"
  });
}

// ../blocks/src/system/runtime/components/booking/DatePicker.tsx
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
var DatePicker = ({
  value,
  onChange,
  dateOptions,
  required = true,
  isLoading = false,
  isEmpty = false,
  hasMore = false,
  onLoadMore
}) => {
  if (isLoading) {
    return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
      /* @__PURE__ */ jsxs3("label", { className: "form-label", children: [
        "Select Date",
        required && /* @__PURE__ */ jsx5("span", { className: "required-marker", children: "*" })
      ] }),
      /* @__PURE__ */ jsx5("div", { className: "h-10 w-full animate-pulse rounded-control card-surface" }),
      /* @__PURE__ */ jsx5("p", { className: "text-xs status-muted", children: "Loading available dates..." })
    ] });
  }
  if (isEmpty) {
    return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
      /* @__PURE__ */ jsxs3("label", { className: "form-label", children: [
        "Select Date",
        required && /* @__PURE__ */ jsx5("span", { className: "required-marker", children: "*" })
      ] }),
      /* @__PURE__ */ jsx5("div", { className: "alert alert-warning", children: /* @__PURE__ */ jsxs3("p", { className: "text-sm", children: [
        "No available dates in this period.",
        hasMore && onLoadMore && /* @__PURE__ */ jsx5(
          "button",
          {
            type: "button",
            onClick: onLoadMore,
            className: "ml-2 underline hover:no-underline",
            children: "Check later dates"
          }
        )
      ] }) })
    ] });
  }
  return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
    /* @__PURE__ */ jsxs3("label", { htmlFor: "booking-date", className: "form-label", children: [
      "Select Date",
      required && /* @__PURE__ */ jsx5("span", { className: "required-marker", children: "*" })
    ] }),
    /* @__PURE__ */ jsxs3(
      "select",
      {
        id: "booking-date",
        value,
        onChange: (e) => onChange(e.target.value),
        required,
        className: "form-select",
        children: [
          /* @__PURE__ */ jsx5("option", { value: "", children: "Choose a date..." }),
          dateOptions.map((date) => /* @__PURE__ */ jsx5("option", { value: date, children: formatDate(date) }, date))
        ]
      }
    ),
    hasMore && onLoadMore && /* @__PURE__ */ jsx5(
      "button",
      {
        type: "button",
        onClick: onLoadMore,
        className: "button-link text-sm",
        children: "Load more dates \u2192"
      }
    )
  ] });
};

// ../blocks/src/system/runtime/components/booking/TimeSlotSelector.tsx
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
var TimeSlotSelector = ({
  value,
  onChange,
  slots,
  isLoading,
  required = true
}) => {
  return /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
    /* @__PURE__ */ jsxs4("label", { htmlFor: "booking-slot", className: "form-label", children: [
      "Select Time",
      required && /* @__PURE__ */ jsx6("span", { className: "required-marker", children: "*" })
    ] }),
    isLoading ? /* @__PURE__ */ jsx6("div", { className: "alert alert-info text-sm text-center", children: "Loading available times..." }) : slots.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "alert text-sm text-center status-muted", children: "No available times for this date" }) : /* @__PURE__ */ jsxs4(
      "select",
      {
        id: "booking-slot",
        value,
        onChange: (e) => onChange(e.target.value),
        required,
        className: "form-select",
        children: [
          /* @__PURE__ */ jsx6("option", { value: "", children: "Choose a time..." }),
          slots.map((slot) => /* @__PURE__ */ jsx6("option", { value: slot.startAt, children: formatTime(slot.startAt) }, slot.startAt))
        ]
      }
    )
  ] });
};

// ../blocks/src/system/runtime/hooks/useAvailableSlots.ts
import { useState as useState3, useEffect as useEffect3 } from "react";
function useAvailableSlots({
  siteId,
  serviceId,
  resourceId,
  selectedDate,
  timezone
}) {
  const [slots, setSlots] = useState3([]);
  const [isLoading, setIsLoading] = useState3(false);
  const [error, setError] = useState3(null);
  useEffect3(() => {
    if (!selectedDate || !serviceId) {
      setSlots([]);
      return;
    }
    const fetchSlots = async () => {
      setIsLoading(true);
      setError(null);
      setSlots([]);
      try {
        const params = new URLSearchParams({
          siteId,
          serviceId,
          startDate: selectedDate,
          endDate: selectedDate,
          timezone
        });
        if (resourceId) {
          params.append("resourceId", resourceId);
        }
        const apiUrl = getCmsApiUrl();
        const response = await fetch(`${apiUrl}/public/bookings/availability/slots?${params}`, {
          method: "GET",
          headers: {
            "Content-Type": "application/json"
          }
        });
        if (!response.ok) {
          const errorData = await response.json().catch(() => ({}));
          throw new Error(errorData.error || "Failed to load available times");
        }
        const data = await response.json();
        setSlots(data.slots);
      } catch (err) {
        setError(err instanceof Error ? err.message : "Failed to load available times");
      } finally {
        setIsLoading(false);
      }
    };
    fetchSlots();
  }, [selectedDate, serviceId, resourceId, timezone, siteId]);
  return { slots, isLoading, error };
}

// ../blocks/src/system/runtime/hooks/useAvailableDates.ts
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3, useRef } from "react";
function useAvailableDates({
  siteId,
  serviceId,
  resourceId,
  timezone = "UTC",
  initialDays = 30
}) {
  const [availableDates, setAvailableDates] = useState4(/* @__PURE__ */ new Set());
  const [isLoading, setIsLoading] = useState4(false);
  const [error, setError] = useState4(null);
  const [hasMore, setHasMore] = useState4(false);
  const [loadedRange, setLoadedRange] = useState4(null);
  const nextStartDateRef = useRef(null);
  const requestIdRef = useRef(0);
  const fetchDates = useCallback3(
    async (startDate, endDate, append = false) => {
      if (!serviceId) {
        console.log("[useAvailableDates] No serviceId, skipping fetch");
        return;
      }
      const requestId = ++requestIdRef.current;
      setIsLoading(true);
      setError(null);
      try {
        const apiUrl = getCmsApiUrl();
        const params = new URLSearchParams({
          siteId,
          serviceId,
          startDate,
          endDate,
          timezone
        });
        if (resourceId) {
          params.append("resourceId", resourceId);
        }
        const url = `${apiUrl}/public/bookings/availability/dates?${params}`;
        console.log("[useAvailableDates] Fetching:", url);
        const res = await fetch(url, {
          method: "GET",
          headers: {
            "Content-Type": "application/json"
          }
        });
        console.log("[useAvailableDates] Response status:", res.status);
        if (requestId !== requestIdRef.current) return;
        if (!res.ok) {
          const errorData = await res.json().catch(() => ({}));
          console.log("[useAvailableDates] Error response:", errorData);
          throw new Error(errorData.error || "Failed to fetch available dates");
        }
        const data = await res.json();
        console.log("[useAvailableDates] Received dates:", data);
        setAvailableDates((prev) => {
          const newSet = append ? new Set(prev) : /* @__PURE__ */ new Set();
          data.dates.forEach((d) => newSet.add(d));
          console.log("[useAvailableDates] Updated availableDates Set size:", newSet.size);
          return newSet;
        });
        setHasMore(data.hasMore);
        nextStartDateRef.current = data.nextStartDate || null;
        setLoadedRange((prev) => ({
          start: append && prev ? prev.start : data.startDate,
          end: data.endDate
        }));
      } catch (err) {
        if (requestId !== requestIdRef.current) return;
        setError(err instanceof Error ? err.message : "Failed to load available dates");
      } finally {
        if (requestId === requestIdRef.current) {
          setIsLoading(false);
        }
      }
    },
    [siteId, serviceId, resourceId, timezone]
  );
  useEffect4(() => {
    if (!serviceId) {
      setAvailableDates(/* @__PURE__ */ new Set());
      setLoadedRange(null);
      setHasMore(false);
      return;
    }
    const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
    const endDate = new Date(Date.now() + initialDays * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
    fetchDates(today, endDate, false);
  }, [serviceId, fetchDates, initialDays]);
  const loadMore = useCallback3(() => {
    if (!nextStartDateRef.current || isLoading) return;
    const start = nextStartDateRef.current;
    const msPerDay = 24 * 60 * 60 * 1e3;
    const end = new Date(new Date(start).getTime() + BOOKING_FETCH_CHUNK_DAYS * msPerDay).toISOString().split("T")[0];
    fetchDates(start, end, true);
  }, [fetchDates, isLoading]);
  return {
    availableDates,
    isLoading,
    error,
    hasMore,
    loadMore,
    loadedRange
  };
}

// ../blocks/src/system/runtime/components/booking/DateTimeSelectionStep.tsx
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
function DateTimeSelectionStep({
  siteId,
  preSelectedServiceId,
  preSelectedResourceId,
  singleService
}) {
  const { data, updateData } = useMultiStepData();
  const serviceId = data.serviceId || preSelectedServiceId;
  const resourceId = data.resourceId || preSelectedResourceId;
  const selectedDate = data.selectedDate || "";
  const selectedSlot = data.selectedSlot || "";
  useEffect5(() => {
    if (preSelectedServiceId && !data.serviceId) {
      updateData({ serviceId: preSelectedServiceId });
    }
  }, [preSelectedServiceId, data.serviceId, updateData]);
  const {
    availableDates,
    isLoading: isLoadingDates,
    error: datesError,
    hasMore,
    loadMore
  } = useAvailableDates({
    siteId,
    serviceId,
    resourceId,
    timezone: "UTC",
    initialDays: 30
  });
  const { slots, isLoading: isLoadingSlots, error: slotsError } = useAvailableSlots({
    siteId,
    serviceId,
    resourceId,
    selectedDate,
    timezone: "UTC"
  });
  const filteredDateOptions = useMemo2(() => {
    if (isLoadingDates || availableDates.size === 0) {
      return [];
    }
    return [...availableDates].sort();
  }, [availableDates, isLoadingDates]);
  const handleDateChange = (date) => {
    updateData({ selectedDate: date, selectedSlot: "" });
  };
  const handleSlotChange = (slot) => {
    updateData({ selectedSlot: slot });
  };
  const error = datesError || slotsError;
  return /* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
    singleService && /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-muted/30 p-4", children: [
      /* @__PURE__ */ jsx7("h3", { className: "font-medium text-foreground", children: singleService.title }),
      singleService.description && /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-muted-foreground", children: singleService.description }),
      singleService.durationMinutes && /* @__PURE__ */ jsxs5("p", { className: "mt-1 text-sm text-muted-foreground", children: [
        "Duration: ",
        singleService.durationMinutes,
        " minutes"
      ] })
    ] }),
    error && /* @__PURE__ */ jsx7("div", { className: "alert alert-error text-sm", children: error }),
    /* @__PURE__ */ jsx7(
      DatePicker,
      {
        value: selectedDate,
        onChange: handleDateChange,
        dateOptions: filteredDateOptions,
        isLoading: isLoadingDates,
        isEmpty: !isLoadingDates && filteredDateOptions.length === 0,
        hasMore,
        onLoadMore: loadMore
      }
    ),
    selectedDate && /* @__PURE__ */ jsx7(
      TimeSlotSelector,
      {
        value: selectedSlot,
        onChange: handleSlotChange,
        slots,
        isLoading: isLoadingSlots
      }
    )
  ] });
}

// ../blocks/src/system/runtime/components/multi-step/DynamicFormFields.tsx
import { useState as useState5, useCallback as useCallback4 } from "react";
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
function DynamicFormFields({
  fields,
  showLabels = true,
  className = ""
}) {
  const { data, updateData } = useMultiStepData();
  const [touched, setTouched] = useState5({});
  const handleChange = useCallback4(
    (fieldId, value) => {
      updateData({ [fieldId]: value });
    },
    [updateData]
  );
  const handleBlur = useCallback4((fieldId) => {
    setTouched((prev) => ({ ...prev, [fieldId]: true }));
  }, []);
  const getFieldError = useCallback4(
    (field) => {
      const value = data[field.id];
      const isTouched = touched[field.id];
      if (!isTouched) return null;
      if (field.required && !value) {
        return `${field.label} is required`;
      }
      if (value) {
        switch (field.type) {
          case "email":
            if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
              return "Please enter a valid email address";
            }
            break;
          case "url":
            try {
              new URL(value);
            } catch {
              return "Please enter a valid URL";
            }
            break;
          case "tel":
            if (!/^[+\d\s\-()]+$/.test(value)) {
              return "Please enter a valid phone number";
            }
            break;
          case "number":
            const num = Number(value);
            if (isNaN(num)) {
              return "Please enter a valid number";
            }
            if (field.min !== void 0 && num < field.min) {
              return `Value must be at least ${field.min}`;
            }
            if (field.max !== void 0 && num > field.max) {
              return `Value must be at most ${field.max}`;
            }
            break;
        }
        if (typeof value === "string") {
          if (field.minLength && value.length < field.minLength) {
            return `Must be at least ${field.minLength} characters`;
          }
          if (field.maxLength && value.length > field.maxLength) {
            return `Must be at most ${field.maxLength} characters`;
          }
        }
        if (field.pattern && typeof value === "string") {
          const regex = new RegExp(field.pattern);
          if (!regex.test(value)) {
            return "Please match the required format";
          }
        }
      }
      return null;
    },
    [data, touched]
  );
  return /* @__PURE__ */ jsx8("div", { className: `space-y-6 ${className}`, children: fields.map((field) => {
    const error = getFieldError(field);
    const value = data[field.id] ?? "";
    return /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
      showLabels && /* @__PURE__ */ jsxs6("label", { htmlFor: field.id, className: "form-label", children: [
        field.label,
        field.required && /* @__PURE__ */ jsx8("span", { className: "required-marker", children: "*" })
      ] }),
      field.helpText && /* @__PURE__ */ jsx8("p", { className: "text-sm status-muted", children: field.helpText }),
      /* @__PURE__ */ jsx8(
        FieldInput,
        {
          field,
          value,
          onChange: (val) => handleChange(field.id, val),
          onBlur: () => handleBlur(field.id),
          error
        }
      ),
      error && /* @__PURE__ */ jsx8("p", { className: "text-sm status-error", children: error })
    ] }, field.id);
  }) });
}
function FieldInput({
  field,
  value,
  onChange,
  onBlur,
  error
}) {
  const inputClass = "form-input";
  const textareaClass = "form-textarea";
  const selectClass = "form-select";
  const checkboxClass = "form-checkbox";
  const radioClass = "form-radio";
  switch (field.type) {
    case "textarea":
      return /* @__PURE__ */ jsx8(
        "textarea",
        {
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          placeholder: field.placeholder,
          required: field.required,
          minLength: field.minLength,
          maxLength: field.maxLength,
          rows: 4,
          "aria-invalid": error ? "true" : void 0,
          className: textareaClass
        }
      );
    case "select":
      if (field.multiple) {
        return /* @__PURE__ */ jsx8(
          "select",
          {
            id: field.id,
            name: field.id,
            value: Array.isArray(value) ? value : [],
            onChange: (e) => {
              const selected = Array.from(e.target.selectedOptions, (opt) => opt.value);
              onChange(selected);
            },
            onBlur,
            required: field.required,
            multiple: true,
            "aria-invalid": error ? "true" : void 0,
            className: `${selectClass} h-32`,
            children: field.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
          }
        );
      }
      return /* @__PURE__ */ jsxs6(
        "select",
        {
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          required: field.required,
          "aria-invalid": error ? "true" : void 0,
          className: selectClass,
          children: [
            /* @__PURE__ */ jsx8("option", { value: "", children: "Select an option..." }),
            field.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
          ]
        }
      );
    case "radio":
      return /* @__PURE__ */ jsx8("div", { className: "space-y-2", children: field.options?.map((opt) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 cursor-pointer", children: [
        /* @__PURE__ */ jsx8(
          "input",
          {
            type: "radio",
            name: field.id,
            value: opt.value,
            checked: value === opt.value,
            onChange: (e) => onChange(e.target.value),
            onBlur,
            required: field.required,
            className: radioClass
          }
        ),
        /* @__PURE__ */ jsx8("span", { className: "text-sm", children: opt.label })
      ] }, opt.value)) });
    case "checkbox":
      if (field.options && field.options.length > 1) {
        const checkedValues = Array.isArray(value) ? value : [];
        return /* @__PURE__ */ jsx8("div", { className: "space-y-2", children: field.options.map((opt) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 cursor-pointer", children: [
          /* @__PURE__ */ jsx8(
            "input",
            {
              type: "checkbox",
              name: field.id,
              value: opt.value,
              checked: checkedValues.includes(opt.value),
              onChange: (e) => {
                const newValues = e.target.checked ? [...checkedValues, opt.value] : checkedValues.filter((v) => v !== opt.value);
                onChange(newValues);
              },
              onBlur,
              className: checkboxClass
            }
          ),
          /* @__PURE__ */ jsx8("span", { className: "text-sm", children: opt.label })
        ] }, opt.value)) });
      }
      return /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 cursor-pointer", children: [
        /* @__PURE__ */ jsx8(
          "input",
          {
            type: "checkbox",
            id: field.id,
            name: field.id,
            checked: !!value,
            onChange: (e) => onChange(e.target.checked),
            onBlur,
            required: field.required,
            className: checkboxClass
          }
        ),
        /* @__PURE__ */ jsx8("span", { className: "text-sm", children: field.placeholder || field.label })
      ] });
    case "consent":
      return /* @__PURE__ */ jsxs6("label", { className: "flex items-start gap-2 cursor-pointer", children: [
        /* @__PURE__ */ jsx8(
          "input",
          {
            type: "checkbox",
            id: field.id,
            name: field.id,
            checked: !!value,
            onChange: (e) => onChange(e.target.checked),
            onBlur,
            required: field.required,
            className: `${checkboxClass} mt-0.5`
          }
        ),
        /* @__PURE__ */ jsx8("span", { className: "text-sm", children: field.placeholder || field.label })
      ] });
    case "number":
      return /* @__PURE__ */ jsx8(
        "input",
        {
          type: "number",
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          placeholder: field.placeholder,
          required: field.required,
          min: field.min,
          max: field.max,
          "aria-invalid": error ? "true" : void 0,
          className: inputClass
        }
      );
    case "date":
      return /* @__PURE__ */ jsx8(
        "input",
        {
          type: "date",
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          required: field.required,
          min: field.min ? String(field.min) : void 0,
          max: field.max ? String(field.max) : void 0,
          "aria-invalid": error ? "true" : void 0,
          className: inputClass
        }
      );
    case "time":
      return /* @__PURE__ */ jsx8(
        "input",
        {
          type: "time",
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          required: field.required,
          "aria-invalid": error ? "true" : void 0,
          className: inputClass
        }
      );
    // text, email, tel, url
    default:
      return /* @__PURE__ */ jsx8(
        "input",
        {
          type: field.type,
          id: field.id,
          name: field.id,
          value,
          onChange: (e) => onChange(e.target.value),
          onBlur,
          placeholder: field.placeholder,
          required: field.required,
          minLength: field.minLength,
          maxLength: field.maxLength,
          pattern: field.pattern,
          "aria-invalid": error ? "true" : void 0,
          className: inputClass
        }
      );
  }
}

// ../blocks/src/system/runtime/hooks/useBookingSteps.ts
function useBookingSteps(siteId, formConfig, services) {
  return useMemo3(() => {
    if (!formConfig) return [];
    const stepsArray = [];
    const effectiveServiceId = formConfig.settings?.serviceId || (services.length === 1 ? services[0].id : void 0);
    const needsServiceSelection = !formConfig.settings?.serviceId && services.length > 1 || formConfig.settings?.serviceIds && formConfig.settings.serviceIds.length > 1;
    if (needsServiceSelection) {
      stepsArray.push({
        id: "service-selection",
        title: "Select Service",
        component: React4.createElement(ServiceSelectionStep, { siteId, services }),
        condition: () => needsServiceSelection,
        validate: (data) => {
          if (!data.serviceId) {
            return { valid: false, errors: { _form: "Please select a service" } };
          }
          return { valid: true };
        }
      });
    }
    const singleService = services.length === 1 ? services[0] : void 0;
    stepsArray.push({
      id: "datetime-selection",
      title: "Select Date & Time",
      component: React4.createElement(DateTimeSelectionStep, {
        siteId,
        preSelectedServiceId: effectiveServiceId,
        preSelectedResourceId: formConfig.settings?.resourceId,
        singleService
      }),
      validate: (data) => {
        if (!data.selectedSlot) {
          return { valid: false, errors: { _form: "Please select a date and time" } };
        }
        return { valid: true };
      }
    });
    if (formConfig.schema?.fields && formConfig.schema.fields.length > 0) {
      stepsArray.push({
        id: "custom-fields",
        title: "Your Information",
        component: React4.createElement(DynamicFormFields, { fields: formConfig.schema.fields }),
        validate: (data) => {
          const errors = {};
          for (const field of formConfig.schema.fields) {
            if (field.required && !data[field.id]) {
              errors[field.id] = `${field.label} is required`;
            }
          }
          if (Object.keys(errors).length > 0) {
            return { valid: false, errors };
          }
          return { valid: true };
        }
      });
    }
    return stepsArray;
  }, [formConfig, services, siteId]);
}

// ../blocks/src/system/runtime/hooks/useBookingSubmission.ts
import { useState as useState6 } from "react";
function useBookingSubmission(siteId) {
  const [isSubmitting, setIsSubmitting] = useState6(false);
  const [error, setError] = useState6(null);
  const [isSuccess, setIsSuccess] = useState6(false);
  const submit = async (data) => {
    setIsSubmitting(true);
    setError(null);
    try {
      const apiUrl = getCmsApiUrl();
      const response = await fetch(`${apiUrl}/public/bookings/appointments`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          formId: data.formId,
          serviceId: data.serviceId,
          resourceId: data.resourceId || null,
          startAt: data.startAt,
          endAt: data.endAt,
          customFields: data.customFields,
          timezone: data.timezone
        })
      });
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.error || "Failed to book appointment");
      }
      setIsSuccess(true);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to book appointment");
      throw err;
    } finally {
      setIsSubmitting(false);
    }
  };
  return { submit, isSubmitting, error, isSuccess };
}

// ../blocks/src/system/runtime/nodes/booking-form.client.tsx
import { jsx as jsx9 } from "react/jsx-runtime";
var BookingFormClient = ({
  siteId: siteIdProp,
  formId,
  className,
  form,
  services = []
}) => {
  const [isSuccess, setIsSuccess] = useState7(false);
  const siteId = siteIdProp || form?.siteId || "";
  React5.useEffect(() => {
    if (!siteId) {
      console.error("[BookingFormClient] ERROR: siteId is missing!");
    }
    if (!form) {
      console.warn("[BookingFormClient] Form data not loaded");
    }
    console.log("[BookingFormClient] Loaded with:", {
      siteId,
      formId,
      hasForm: !!form,
      servicesCount: Array.isArray(services) ? services.length : "not an array",
      servicesData: services
    });
  }, [siteId, formId, form, services]);
  const formConfig = form ? {
    id: form.id,
    name: form.name,
    slug: "",
    // Not needed for booking form
    settings: form.settingsJson,
    schema: form.schemaJson
  } : null;
  const normalizedServices = React5.useMemo(() => {
    if (Array.isArray(services)) {
      return services;
    }
    if (services && typeof services === "object") {
      if ("services" in services) {
        return services.services || [];
      }
      return Object.values(services);
    }
    return [];
  }, [services]);
  const steps = useBookingSteps(siteId, formConfig, normalizedServices);
  const { submit } = useBookingSubmission(siteId);
  if (!form) {
    return /* @__PURE__ */ jsx9("div", { className: `rounded-lg border border-destructive bg-destructive/10 p-4 ${className ?? ""}`, children: /* @__PURE__ */ jsx9("p", { className: "text-sm text-destructive", children: "Booking form not found. Please check your configuration." }) });
  }
  const handleComplete = async (data) => {
    try {
      const serviceId = data.serviceId || formConfig?.settings?.serviceId;
      const resourceId = data.resourceId || formConfig?.settings?.resourceId;
      if (!serviceId || !data.selectedSlot) {
        throw new Error("Missing required booking information");
      }
      console.log("[BookingFormClient] Looking for service:", {
        serviceId,
        availableServices: normalizedServices
      });
      const selectedService = normalizedServices.find((s) => s.id === serviceId);
      console.log("[BookingFormClient] Selected service:", selectedService);
      if (!selectedService) {
        throw new Error(`Service not found: ${serviceId}`);
      }
      if (!selectedService.durationMinutes) {
        throw new Error(
          `Service "${selectedService.title || serviceId}" is missing duration. Please update the service in the dashboard to include a duration (e.g., 30 minutes).`
        );
      }
      const startDate = new Date(data.selectedSlot);
      const endDate = new Date(startDate.getTime() + selectedService.durationMinutes * 60 * 1e3);
      const bookingFields = /* @__PURE__ */ new Set(["serviceId", "resourceId", "selectedDate", "selectedSlot"]);
      const customFields = {};
      for (const [key, value] of Object.entries(data)) {
        if (!bookingFields.has(key)) {
          customFields[key] = value;
        }
      }
      await submit({
        formId,
        serviceId,
        resourceId,
        startAt: data.selectedSlot,
        endAt: endDate.toISOString(),
        customFields,
        timezone: "UTC"
        // TODO: Get from resource or user preference
      });
      setIsSuccess(true);
    } catch (err) {
      console.error("Booking submission failed:", err);
      throw err;
    }
  };
  if (isSuccess) {
    const successMessage = formConfig?.settings?.successMessage || "Your appointment has been booked! Check your email for confirmation.";
    return /* @__PURE__ */ jsx9(SuccessMessage, { message: successMessage, className });
  }
  return /* @__PURE__ */ jsx9("div", { className, children: /* @__PURE__ */ jsx9(
    MultiStepForm,
    {
      steps,
      initialData: {
        serviceId: formConfig?.settings?.serviceId,
        resourceId: formConfig?.settings?.resourceId
      },
      onComplete: handleComplete,
      progressStyle: "steps",
      persistToUrl: false,
      allowSkip: false
    }
  ) });
};

// ../blocks/src/system/runtime/hooks/useBookingFormConfig.ts
import { useState as useState8, useEffect as useEffect6 } from "react";
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function useBookingFormConfig(siteId, formId) {
  const [formConfig, setFormConfig] = useState8(null);
  const [services, setServices] = useState8([]);
  const [isLoading, setIsLoading] = useState8(true);
  const [error, setError] = useState8(null);
  useEffect6(() => {
    let mounted = true;
    async function loadFormAndServices() {
      try {
        setIsLoading(true);
        setError(null);
        const apiUrl = getCmsApiUrl();
        const isUUID = UUID_REGEX.test(formId);
        const formUrl = isUUID ? `${apiUrl}/public/forms/${formId}` : `${apiUrl}/public/forms/${formId}?siteId=${encodeURIComponent(siteId)}`;
        const response = await fetch(formUrl);
        if (!response.ok) {
          throw new Error("Failed to load booking form");
        }
        const { form: rawData } = await response.json();
        if (!mounted) return;
        const normalizedForm = {
          id: rawData.id,
          name: rawData.name,
          slug: rawData.slug,
          settings: rawData.settingsJson ?? rawData.settings,
          schema: rawData.schemaJson ?? rawData.schema
        };
        setFormConfig(normalizedForm);
        if (!normalizedForm.settings?.serviceId && !normalizedForm.settings?.serviceIds) {
          const servicesResponse = await fetch(
            `${apiUrl}/public/bookings/services?siteId=${encodeURIComponent(siteId)}`
          );
          if (servicesResponse.ok) {
            const servicesData = await servicesResponse.json();
            if (mounted) {
              setServices(servicesData.services || []);
            }
          }
        } else if (normalizedForm.settings?.serviceIds && normalizedForm.settings.serviceIds.length > 0) {
          const servicesResponse = await fetch(
            `${apiUrl}/public/bookings/services?siteId=${encodeURIComponent(siteId)}&ids=${normalizedForm.settings.serviceIds.join(",")}`
          );
          if (servicesResponse.ok) {
            const servicesData = await servicesResponse.json();
            if (mounted) {
              setServices(servicesData.services || []);
            }
          }
        } else if (normalizedForm.settings?.serviceId) {
          const serviceResponse = await fetch(
            `${apiUrl}/public/bookings/services/${normalizedForm.settings.serviceId}?siteId=${encodeURIComponent(siteId)}`
          );
          if (serviceResponse.ok) {
            const serviceData = await serviceResponse.json();
            if (mounted) {
              setServices([serviceData.service || serviceData]);
            }
          }
        }
      } catch (err) {
        console.error("Failed to load form:", err);
        if (mounted) {
          setError("Failed to load booking form. Please try again later.");
        }
      } finally {
        if (mounted) {
          setIsLoading(false);
        }
      }
    }
    loadFormAndServices();
    return () => {
      mounted = false;
    };
  }, [siteId, formId]);
  return { formConfig, services, isLoading, error };
}
export {
  BookingFormClient,
  useBookingFormConfig
};
//# sourceMappingURL=bookings.mjs.map