import PlayzerIcon from '@/assets/playzer-favicon-512-transparent.png';
import { Button } from '@/components/ui/button';
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import {
  Stepper,
  StepperIndicator,
  StepperItem,
  StepperList,
  StepperSeparator,
  StepperTitle,
  StepperTrigger,
} from '@/components/ui/stepper';
import { useIsMobile } from '@/hooks/use-mobile';
import { ApiClientError, apiClient } from '@/lib/api-client';
import { useTheme } from '@/lib/theme';
import { zodResolver } from '@hookform/resolvers/zod';
import type { PublicAvailabilityCourt, PublicBookingSport } from '@repo/api-contract';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import {
  ArrowRight,
  Building2,
  CalendarDays,
  Camera,
  Check,
  ChevronLeft,
  ChevronRight,
  Dumbbell,
  Lock,
  MapPin,
  MessageCircle,
  Moon,
  Share2,
  ShieldCheck,
  Sparkles,
  Sun,
  Trophy,
  Users,
  Zap,
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

const DAYS_STEP = 5;
const DESKTOP_DAYS_STEP = 7;
const BUSINESS_START_MINUTES = 8 * 60;
const BUSINESS_END_MINUTES = 22 * 60;

const bookingFormSchema = z.object({
  customerName: z
    .string()
    .trim()
    .min(2, 'Ingresa tu nombre.')
    .max(120, 'El nombre no puede superar los 120 caracteres.'),
  customerPhone: z
    .string()
    .trim()
    .min(6, 'Ingresá un teléfono válido.')
    .max(30, 'El teléfono no puede superar los 30 caracteres.'),
  customerEmail: z.string().email('Ingresá un email válido.'),
});

type BookingFormValues = z.infer<typeof bookingFormSchema>;

type PublicBookingPageProps = {
  complexSlug: string;
};

type SelectedSlot = {
  courtId: string;
  courtName: string;
  sportId: string;
  sportName: string;
  startTime: string;
  endTime: string;
  price: number;
};

type BookingShellProps = {
  complexName?: string;
  complexAddress?: string;
  sports: PublicBookingSport[];
  courts: PublicAvailabilityCourt[];
  selectedDate: string;
  selectedSportId?: string;
  selectedCourtId?: string;
  selectedSlot: SelectedSlot | null;
  dayOptions: DayOption[];
  canGoBack: boolean;
  isLoading: boolean;
  isError: boolean;
  sportSelectionRequired?: boolean;
  errorMessage?: string;
  autoAdjustedDateNotice: string | null;
  form: ReturnType<typeof useForm<BookingFormValues>>;
  isCreating: boolean;
  createError?: string;
  navigationError: string | null;
  onSelectDate: (date: string) => void;
  onPreviousDays: () => void;
  onNextDays: () => void;
  onPreviousDay: () => void;
  onNextDay: () => void;
  onSelectSport: (sportId: string) => void;
  onSelectCourt: (courtId: string) => void;
  onSelectSlot: (slot: SelectedSlot) => void;
  onSubmit: (values: BookingFormValues) => Promise<void>;
};

type DayOption = {
  value: string;
  date: Date;
  weekday: string;
  dayMonth: string;
  shortDay: string;
  monthLabel: string;
  longLabel: string;
};

function addDays(baseDate: Date, amount: number) {
  const next = new Date(baseDate);
  next.setDate(next.getDate() + amount);
  return next;
}

function toIsoDateLocal(date: Date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function toMinutes(value: string): number {
  const [hours, minutes] = value.split(':').map((part) => Number(part));
  return hours * 60 + minutes;
}

function toTimeLabel(minutes: number) {
  const hours = Math.floor(minutes / 60);
  const mins = minutes % 60;
  return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
}

function formatDayLabel(date: Date): DayOption {
  const weekday = new Intl.DateTimeFormat('es-AR', { weekday: 'short' })
    .format(date)
    .replace('.', '');
  const monthLabel = new Intl.DateTimeFormat('es-AR', { month: 'short' })
    .format(date)
    .replace('.', '');
  const longLabel = new Intl.DateTimeFormat('es-AR', {
    weekday: 'long',
    day: 'numeric',
    month: 'long',
  }).format(date);

  return {
    value: toIsoDateLocal(date),
    date,
    weekday,
    dayMonth: new Intl.DateTimeFormat('es-AR', {
      day: '2-digit',
      month: '2-digit',
    }).format(date),
    shortDay: new Intl.DateTimeFormat('es-AR', { day: '2-digit' }).format(date),
    monthLabel,
    longLabel: longLabel.charAt(0).toUpperCase() + longLabel.slice(1),
  };
}

function extractMessage(error: unknown, fallback: string) {
  if (error instanceof ApiClientError) {
    return error.message || fallback;
  }

  return fallback;
}

function formatBookingPrice(price: number): string {
  if (price === 0) {
    return 'Sin cargo';
  }

  return new Intl.NumberFormat('es-AR', {
    style: 'currency',
    currency: 'ARS',
    maximumFractionDigits: Number.isInteger(price) ? 0 : 2,
  }).format(price);
}

function getStep(selectedSlot: SelectedSlot | null, isValid: boolean) {
  if (!selectedSlot) return 1;
  if (!isValid) return 3;
  return 4;
}

function getCompletedSteps(selectedSlot: SelectedSlot | null, isValid: boolean) {
  return {
    court: Boolean(selectedSlot),
    time: Boolean(selectedSlot),
    details: Boolean(selectedSlot && isValid),
    confirm: false,
  };
}

function getSelectedCourt(
  courts: PublicAvailabilityCourt[],
  selectedCourtId: string | undefined,
  selectedSlot: SelectedSlot | null
) {
  if (selectedSlot) {
    return courts.find((court) => court.courtId === selectedSlot.courtId) ?? courts[0];
  }

  return courts.find((court) => court.courtId === selectedCourtId) ?? courts[0];
}

function getTimelineRange(court?: PublicAvailabilityCourt) {
  if (!court || court.availableSlots.length === 0) {
    return {
      start: BUSINESS_START_MINUTES,
      end: BUSINESS_END_MINUTES,
    };
  }

  const starts = court.availableSlots.map((slot) => toMinutes(slot.startTime));
  const ends = court.availableSlots.map((slot) => toMinutes(slot.endTime));
  const firstStart = Math.min(...starts);
  const lastEnd = Math.max(...ends);

  return {
    start: Math.min(BUSINESS_START_MINUTES, Math.floor(firstStart / 60) * 60),
    end: Math.max(BUSINESS_END_MINUTES, Math.ceil(lastEnd / 60) * 60),
  };
}

function getHourTicks(start: number, end: number, compact = false) {
  const step = compact ? 120 : 60;
  const ticks = [];

  for (let minutes = start; minutes <= end; minutes += step) {
    ticks.push(minutes);
  }

  return ticks;
}

function isSlotSelected(
  slot: { startTime: string },
  courtId: string,
  selectedSlot: SelectedSlot | null
) {
  return selectedSlot?.courtId === courtId && selectedSlot.startTime === slot.startTime;
}

function getSportIcon(sportName: string) {
  const lower = sportName.toLowerCase();
  if (lower.includes('tenis')) return Zap;
  if (lower.includes('futbol') || lower.includes('fútbol')) return Dumbbell;
  if (lower.includes('paddle') || lower.includes('pádel')) return Trophy;
  if (lower.includes('basquet') || lower.includes('básquet')) return Trophy;
  if (lower.includes('volei') || lower.includes('vóley')) return Trophy;
  return Trophy;
}

function PublicBookingThemeToggle() {
  const { resolvedTheme, toggleTheme } = useTheme();
  const Icon = resolvedTheme === 'dark' ? Sun : Moon;

  return (
    <button
      type="button"
      onClick={toggleTheme}
      className="flex size-10 items-center justify-center rounded-lg border border-white/10 bg-white/[0.06] text-white transition hover:bg-white/[0.1]"
      aria-label="Cambiar tema"
    >
      <Icon className="size-5" />
    </button>
  );
}

function PlayzerBrand({ compact = false }: { compact?: boolean }) {
  return (
    <div className="flex items-center gap-2">
      <img src={PlayzerIcon} alt="Playzer" className={compact ? 'size-8' : 'size-9'} />
      <span
        className={
          compact
            ? 'text-lg font-bold tracking-normal text-cyan-300'
            : 'text-2xl font-bold tracking-normal text-white'
        }
      >
        Playzer
      </span>
    </div>
  );
}

function ProgressSteps({
  currentStep,
  completedSteps,
  mobile = false,
}: {
  currentStep: number;
  completedSteps: Record<string, boolean>;
  mobile?: boolean;
}) {
  const steps = [
    { value: 'court', label: 'Elegí cancha' },
    { value: 'time', label: 'Seleccioná horario' },
    { value: 'details', label: 'Tus datos' },
    { value: 'confirm', label: 'Confirmación' },
  ];
  const activeStep = steps[Math.max(0, Math.min(currentStep - 1, steps.length - 1))]?.value;

  return (
    <Stepper
      value={activeStep}
      nonInteractive
      className={mobile ? 'mx-auto w-full max-w-[240px]' : 'max-w-[760px]'}
    >
      <StepperList className="items-center gap-0">
        {steps.map((step, index) => {
          const isCompleted = completedSteps[step.value];
          const isActive = step.value === activeStep;

          return (
            <StepperItem
              key={step.value}
              value={step.value}
              completed={isCompleted}
              className="flex min-w-0 flex-1 items-center last:flex-none"
            >
              <StepperTrigger
                className={`gap-2 rounded-full p-0 ${
                  isCompleted || isActive ? 'text-white' : 'text-white/48'
                }`}
              >
                <StepperIndicator
                  className={`size-7 text-xs font-bold shadow-none ${
                    isCompleted
                      ? 'border-0 bg-emerald-500 text-white'
                      : isActive
                        ? 'border border-emerald-400 bg-white/10 text-white shadow-[0_0_0_4px_rgba(16,185,129,0.12)]'
                        : 'border-0 bg-white/10 text-white/62'
                  }`}
                >
                  {index + 1}
                </StepperIndicator>
                {!mobile && (
                  <StepperTitle className="whitespace-nowrap text-xs font-semibold text-inherit">
                    {step.label}
                  </StepperTitle>
                )}
              </StepperTrigger>
              <StepperSeparator
                className={`mx-3 h-px min-w-6 flex-1 ${
                  isCompleted ? 'bg-emerald-500/80' : 'bg-white/10'
                }`}
              />
            </StepperItem>
          );
        })}
      </StepperList>
    </Stepper>
  );
}

function Legend() {
  return (
    <div className="flex flex-wrap gap-4 text-xs text-white/70">
      <LegendDot color="bg-emerald-500" label="Libre" />
      <LegendDot color="bg-blue-500" label="Reservado" />
    </div>
  );
}

function LegendDot({ color, label }: { color: string; label: string }) {
  return (
    <span className="inline-flex items-center gap-2">
      <span className={`size-2.5 rounded-full ${color}`} />
      {label}
    </span>
  );
}

function CourtIcon({ className = 'size-5' }: { className?: string }) {
  return <Trophy className={className} />;
}

function PublicBookingPageChrome({
  children,
  currentStep,
  completedSteps,
  complexName,
  mobile = false,
}: {
  children: React.ReactNode;
  currentStep: number;
  completedSteps: Record<string, boolean>;
  complexName?: string;
  mobile?: boolean;
}) {
  const { resolvedTheme } = useTheme();
  const themeClass =
    resolvedTheme === 'dark'
      ? 'public-booking-root public-booking-dark'
      : 'public-booking-root public-booking-light';

  if (mobile) {
    return (
      <main className={`${themeClass} min-h-screen bg-[#050d14] text-white`}>
        <div className="public-booking-frame mx-auto min-h-screen max-w-[430px] bg-[radial-gradient(circle_at_50%_0%,rgba(17,185,129,0.16),transparent_34%),linear-gradient(180deg,#07131d_0%,#071018_47%,#050b11_100%)] px-4 pb-0 pt-7 shadow-2xl shadow-black">
          <header className="flex items-center justify-between">
            <PlayzerBrand compact />
            <PublicBookingThemeToggle />
          </header>

          <section className="mt-6 text-center">
            <h1 className="text-balance text-2xl font-bold leading-tight tracking-normal">
              Reservá <span className="text-emerald-400">tu cancha</span>
              <br />
              de forma rápida y sencilla
            </h1>
            <p className="mx-auto mt-2 max-w-[260px] text-sm leading-5 text-white/66">
              Elegí tu cancha, seleccioná el horario y completá tu reserva.
            </p>
            {complexName && (
              <p className="mt-3 text-xs font-semibold uppercase tracking-[0.16em] text-emerald-300/80">
                {complexName}
              </p>
            )}
          </section>

          <div className="mt-5">
            <ProgressSteps currentStep={currentStep} completedSteps={completedSteps} mobile />
          </div>

          {children}
        </div>
      </main>
    );
  }

  return (
    <main className={`${themeClass} min-h-screen bg-[#02070b] text-white`}>
      <div className="public-booking-frame min-h-screen overflow-hidden bg-[radial-gradient(circle_at_18%_12%,rgba(7,172,132,0.16),transparent_28%),radial-gradient(circle_at_82%_20%,rgba(46,116,255,0.12),transparent_30%),linear-gradient(180deg,#07131b_0%,#071019_48%,#050b11_100%)]">
        <header className="border-b border-white/10">
          <div className="mx-auto flex h-20 max-w-[1320px] items-center gap-8 px-10">
            <PlayzerBrand />
            {complexName && (
              <div className="ml-auto hidden min-w-0 items-center gap-3  px-4 py-2 md:flex">
                <div className="min-w-0 text-right">
                  <p className="truncate text-sm font-semibold text-white">{complexName}</p>
                </div>
              </div>
            )}
            <PublicBookingThemeToggle />
          </div>
        </header>

        <section className="mx-auto max-w-[1320px] px-10 py-8">
          <h1 className="max-w-[560px] text-balance text-4xl font-bold leading-tight tracking-normal">
            Reservá <span className="text-emerald-400">tu cancha</span>
            <br />
            de forma rápida y sencilla
          </h1>
          <p className="mt-3 text-sm text-white/68">
            Elegí tu cancha, seleccioná el horario y completá tu reserva.
          </p>
          <div className="mt-7">
            <ProgressSteps currentStep={currentStep} completedSteps={completedSteps} />
          </div>
        </section>

        {children}

        <footer className="mt-8 border-t border-white/10">
          <div className="mx-auto flex max-w-[1320px] items-center gap-6 px-10 py-6">
            <PlayzerBrand />
            <p className="text-xs text-white/45">© 2026 Playzer. Todos los derechos reservados.</p>
            <div className="ml-auto flex gap-3 text-white/55">
              <span className="flex size-10 items-center justify-center rounded-full border border-white/10">
                <Camera className="size-4" />
              </span>
              <span className="flex size-10 items-center justify-center rounded-full border border-white/10">
                <Share2 className="size-4" />
              </span>
              <span className="flex size-10 items-center justify-center rounded-full border border-white/10">
                <MessageCircle className="size-4" />
              </span>
            </div>
          </div>
        </footer>
      </div>
    </main>
  );
}

function ComplexInfoPanel({
  complexName,
  complexAddress,
}: {
  complexName?: string;
  complexAddress?: string;
}) {
  return (
    <Panel className="p-5">
      <div className="flex items-start gap-4">
        <div className="flex size-12 shrink-0 items-center justify-center rounded-full border border-emerald-500/40 bg-emerald-500/12">
          <Building2 className="size-6 text-emerald-400" />
        </div>
        <div className="min-w-0">
          <h2 className="text-lg font-semibold text-white">{complexName ?? 'Complex'}</h2>
          {complexAddress && (
            <p className="mt-1 flex items-start gap-2 text-sm text-white/62">
              <MapPin className="mt-0.5 size-4 shrink-0 text-emerald-400" />
              <span className="leading-relaxed">{complexAddress}</span>
            </p>
          )}
        </div>
      </div>
    </Panel>
  );
}

function PublicBookingDesktop(props: BookingShellProps) {
  const {
    courts,
    sports,
    selectedSportId,
    selectedCourtId,
    selectedDate,
    selectedSlot,
    dayOptions,
    canGoBack,
    isLoading,
    isError,
    sportSelectionRequired,
    errorMessage,
    autoAdjustedDateNotice,
    onSelectSport,
    onSelectCourt,
    onSelectSlot,
  } = props;
  const selectedCourt = getSelectedCourt(courts, selectedCourtId, selectedSlot);
  const currentStep = getStep(selectedSlot, props.form.formState.isValid);
  const completedSteps = getCompletedSteps(selectedSlot, props.form.formState.isValid);
  const selectedDay = dayOptions.find((option) => option.value === selectedDate);
  const canGoBackButton = canGoBack;

  return (
    <PublicBookingPageChrome
      currentStep={currentStep}
      completedSteps={completedSteps}
      complexName={props.complexName}
    >
      <div className="mx-auto grid max-w-[1320px] grid-cols-[330px_minmax(0,1fr)] gap-5 px-10">
        <aside className="space-y-4">
          <ComplexInfoPanel complexName={props.complexName} complexAddress={props.complexAddress} />
          <Panel className="p-5">
            <h2 className="text-lg font-semibold">Seleccioná tu cancha</h2>
            <div className="mt-5 space-y-4">
              {sportSelectionRequired && (
                <SportSelector
                  label="Deporte"
                  value={selectedSportId ?? ''}
                  onValueChange={onSelectSport}
                  options={sports.map((sport) => ({ value: sport.id, label: sport.name }))}
                />
              )}
              <div>
                <p className="mb-2 text-sm text-white/76">Canchas disponibles</p>
                <div id="canchas" className="space-y-2">
                  {courts.map((court) => (
                    <button
                      key={court.courtId}
                      type="button"
                      onClick={() => onSelectCourt(court.courtId)}
                      className={`flex w-full items-center gap-3 rounded-md border p-3 text-left transition ${
                        selectedCourt?.courtId === court.courtId
                          ? 'border-emerald-500 bg-emerald-500/18 shadow-[inset_0_0_20px_rgba(16,185,129,0.14)]'
                          : 'border-white/10 bg-white/[0.035] hover:border-white/20 hover:bg-white/[0.055]'
                      }`}
                    >
                      <CourtIcon className="size-5 text-white" />
                      <span className="min-w-0 flex-1">
                        <span className="block truncate text-sm font-semibold">
                          {court.courtName}
                        </span>
                        <span className="block text-xs text-white/54">{court.sport.name}</span>
                      </span>
                      {selectedCourt?.courtId === court.courtId && (
                        <span className="flex size-5 items-center justify-center rounded-full bg-white text-emerald-600">
                          <Check className="size-3.5" />
                        </span>
                      )}
                    </button>
                  ))}
                  {!isLoading && courts.length === 0 && (
                    <p className="rounded-md border border-white/10 bg-white/[0.035] p-3 text-sm text-white/62">
                      No hay canchas disponibles para esta fecha.
                    </p>
                  )}
                </div>
              </div>
            </div>
          </Panel>

          <Panel className="p-5">
            <div id="como-funciona" className="space-y-4">
              <Feature
                icon={<Sparkles className="size-6" />}
                title="Reserva en pocos pasos"
                text="Completá tu reserva en menos de 2 minutos"
              />
              <Feature
                icon={<ShieldCheck className="size-6" />}
                title="Cancelación flexible"
                text="Cancelá gratis hasta 2 horas antes"
              />
              <Feature
                icon={<Users className="size-6" />}
                title="Atención personalizada"
                text="Estamos disponibles para ayudarte"
              />
            </div>
          </Panel>
        </aside>

        <section className="space-y-4">
          {autoAdjustedDateNotice && (
            <p className="rounded-md border border-amber-300/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
              {autoAdjustedDateNotice}
            </p>
          )}
          {isError && <StatusPanel>{errorMessage}</StatusPanel>}
          {isLoading && <StatusPanel>Buscando disponibilidad...</StatusPanel>}
          {selectedCourt && (
            <>
              <Panel className="overflow-hidden">
                <div className="flex items-center border-b border-white/10 px-6 py-5">
                  <CourtHeader court={selectedCourt} complexName={props.complexName} />
                  <div className="ml-auto flex items-center overflow-hidden rounded-md border border-white/10 bg-white/[0.035] text-sm">
                    <button
                      type="button"
                      className="flex size-11 items-center justify-center border-r border-white/10 transition hover:bg-white/[0.06]"
                      onClick={props.onPreviousDay}
                      disabled={!canGoBackButton}
                      aria-label="Día anterior"
                    >
                      <ChevronLeft className="size-5" />
                    </button>
                    <div className="flex h-11 min-w-[250px] items-center justify-center gap-2 px-4">
                      <CalendarDays className="size-4 text-white/76" />
                      <span>{selectedDay?.longLabel ?? selectedDate}</span>
                    </div>
                    <button
                      type="button"
                      className="flex size-11 items-center justify-center border-l border-white/10 transition hover:bg-white/[0.06]"
                      onClick={props.onNextDay}
                      aria-label="Día siguiente"
                    >
                      <ChevronRight className="size-5" />
                    </button>
                  </div>
                </div>

                <div className="p-6">
                  <Legend />
                  <BookingTimeline
                    court={selectedCourt}
                    selectedSlot={selectedSlot}
                    onSelectSlot={onSelectSlot}
                  />
                </div>
              </Panel>

              <SelectedSlotCard {...props} />
            </>
          )}
        </section>
      </div>
    </PublicBookingPageChrome>
  );
}

function PublicBookingMobile(props: BookingShellProps) {
  const selectedCourt = getSelectedCourt(props.courts, props.selectedCourtId, props.selectedSlot);
  const currentStep = getStep(props.selectedSlot, props.form.formState.isValid);
  const completedSteps = getCompletedSteps(props.selectedSlot, props.form.formState.isValid);
  const selectedDay = props.dayOptions.find((option) => option.value === props.selectedDate);

  return (
    <PublicBookingPageChrome
      currentStep={currentStep}
      completedSteps={completedSteps}
      complexName={props.complexName}
      mobile
    >
      <div className="mt-5 space-y-3">
        <Panel className="p-3">
          <h2 className="text-base font-semibold">Seleccioná tu cancha</h2>
          <div className="mt-3 space-y-0">
            {props.sportSelectionRequired && (
              <MobileSportSelector
                label="Deporte"
                value={props.selectedSportId ?? ''}
                onValueChange={props.onSelectSport}
                options={props.sports.map((sport) => ({ value: sport.id, label: sport.name }))}
              />
            )}
            {props.courts.length > 0 ? (
              <MobileCourtPicker
                courts={props.courts}
                selectedCourtId={selectedCourt?.courtId}
                onSelectCourt={props.onSelectCourt}
              />
            ) : (
              !props.isLoading && (
                <p className="rounded-md border border-white/10 bg-white/[0.035] p-3 text-sm text-white/62">
                  No hay canchas disponibles para esta fecha.
                </p>
              )
            )}
          </div>
        </Panel>

        <Panel className="flex items-center gap-2 p-2">
          <span className="px-2 text-sm text-white/76">Fecha</span>
          <button
            type="button"
            onClick={props.onPreviousDay}
            disabled={!props.canGoBack}
            className="flex size-9 items-center justify-center rounded-md border border-white/10 bg-white/[0.035] disabled:opacity-35"
            aria-label="Día anterior"
          >
            <ChevronLeft className="size-4" />
          </button>
          <div className="flex h-9 min-w-0 flex-1 items-center justify-center gap-2 rounded-md border border-white/10 bg-white/[0.035] px-2 text-xs">
            <CalendarDays className="size-4" />
            <span className="truncate">{selectedDay?.longLabel ?? props.selectedDate}</span>
          </div>
          <button
            type="button"
            onClick={props.onNextDay}
            className="flex size-9 items-center justify-center rounded-md border border-white/10 bg-white/[0.035]"
            aria-label="Día siguiente"
          >
            <ChevronRight className="size-4" />
          </button>
        </Panel>

        {props.autoAdjustedDateNotice && (
          <p className="rounded-md border border-amber-300/30 bg-amber-400/10 px-3 py-2 text-xs text-amber-100">
            {props.autoAdjustedDateNotice}
          </p>
        )}
        {props.isLoading && <StatusPanel>Buscando disponibilidad...</StatusPanel>}
        {props.isError && <StatusPanel>{props.errorMessage}</StatusPanel>}
        {selectedCourt && (
          <Panel className="overflow-hidden">
            <div className="p-3">
              <CourtHeader court={selectedCourt} compact />
            </div>
            <div className="border-t border-white/10 p-3">
              <MobileAvailableSlots
                court={selectedCourt}
                selectedSlot={props.selectedSlot}
                onSelectSlot={props.onSelectSlot}
              />
            </div>
            <div className="px-3 pb-3">
              <SelectedSlotCard {...props} compact />
            </div>
          </Panel>
        )}
      </div>
    </PublicBookingPageChrome>
  );
}

function Panel({ children, className = '' }: { children: React.ReactNode; className?: string }) {
  return (
    <section
      className={`public-booking-panel rounded-md border border-white/10 bg-[#0d1820]/78 shadow-[inset_0_1px_0_rgba(255,255,255,0.04),0_20px_60px_rgba(0,0,0,0.18)] backdrop-blur ${className}`}
    >
      {children}
    </section>
  );
}

function StatusPanel({ children }: { children?: React.ReactNode }) {
  return (
    <Panel className="p-4">
      <p className="text-sm text-white/68">{children}</p>
    </Panel>
  );
}

function SportSelector({
  label,
  value,
  options,
  onValueChange,
}: {
  label: string;
  value: string;
  options: { value: string; label: string }[];
  onValueChange: (value: string) => void;
}) {
  return (
    <Field>
      <FieldLabel className="text-sm text-white/76">{label}</FieldLabel>
      <div className="flex flex-wrap gap-2">
        {options.map((option) => {
          const Icon = getSportIcon(option.label);
          const isSelected = value === option.value;
          return (
            <button
              key={option.value}
              type="button"
              onClick={() => onValueChange(option.value)}
              className={`inline-flex items-center gap-2 rounded-full px-4 py-2 transition-all ${
                isSelected
                  ? 'border border-emerald-500 bg-emerald-500/18 shadow-[inset_0_0_20px_rgba(16,185,129,0.14)] text-white'
                  : 'border border-white/15 bg-white/[0.05] text-white/76 hover:border-white/30 hover:bg-white/[0.08] hover:text-white'
              }`}
            >
              <Icon className="size-4" />
              <span className="text-sm font-medium">{option.label}</span>
            </button>
          );
        })}
      </div>
    </Field>
  );
}

function MobileSportSelector({
  label,
  value,
  options,
  onValueChange,
}: {
  label: string;
  value: string;
  options: { value: string; label: string }[];
  onValueChange: (value: string) => void;
}) {
  return (
    <div className="border-b border-white/10 bg-white/[0.025] px-3 py-3 last:border-b-0">
      <span className="mb-2 block text-xs text-white/66">{label}</span>
      <div className="flex flex-wrap gap-2">
        {options.map((option) => {
          const Icon = getSportIcon(option.label);
          const isSelected = value === option.value;
          return (
            <button
              key={option.value}
              type="button"
              onClick={() => onValueChange(option.value)}
              className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-all ${
                isSelected
                  ? 'border border-emerald-500 bg-emerald-500/18 shadow-[inset_0_0_15px_rgba(16,185,129,0.1)] text-white'
                  : 'border border-white/15 bg-white/[0.05] text-white/66 hover:border-white/25 hover:bg-white/[0.08] hover:text-white/80'
              }`}
            >
              <Icon className="size-3.5" />
              <span>{option.label}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function MobileCourtPicker({
  courts,
  selectedCourtId,
  onSelectCourt,
}: {
  courts: PublicAvailabilityCourt[];
  selectedCourtId?: string;
  onSelectCourt: (courtId: string) => void;
}) {
  return (
    <div className="border-b border-white/10 bg-white/[0.025] px-3 py-3 last:border-b-0">
      <div className="grid grid-cols-2 gap-2">
        {courts.map((court) => {
          const isSelected = selectedCourtId === court.courtId;

          return (
            <button
              key={court.courtId}
              type="button"
              onClick={() => onSelectCourt(court.courtId)}
              aria-pressed={isSelected}
              className={`flex min-h-16 items-start gap-2 rounded-md border p-3 text-left transition ${
                isSelected
                  ? 'border-emerald-400 bg-emerald-500/20 text-white shadow-[inset_0_0_18px_rgba(16,185,129,0.12)]'
                  : 'border-white/10 bg-white/[0.045] text-white/72 active:border-white/25 active:bg-white/[0.075]'
              }`}
            >
              <span
                className={`mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border ${
                  isSelected
                    ? 'border-emerald-300 bg-emerald-400 text-[#061019]'
                    : 'border-white/18 text-transparent'
                }`}
              >
                <Check className="size-3.5" />
              </span>
              <span className="min-w-0">
                <span className="block truncate text-sm font-semibold">{court.courtName}</span>
                <span className="mt-0.5 block truncate text-xs text-white/52">
                  {court.sport.name}
                </span>
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function MobileAvailableSlots({
  court,
  selectedSlot,
  onSelectSlot,
}: {
  court: PublicAvailabilityCourt;
  selectedSlot: SelectedSlot | null;
  onSelectSlot: (slot: SelectedSlot) => void;
}) {
  const slots = court.availableSlots;

  return (
    <div>
      <div className="flex items-end justify-between gap-3">
        <div>
          <h3 className="text-base font-semibold">Horarios disponibles</h3>
          <p className="mt-0.5 text-xs text-white/58">
            Turnos de {court.slotDurationMinutes} minutos
          </p>
        </div>
        <span className="shrink-0 text-xs font-medium text-emerald-300/90">
          {slots.length} {slots.length === 1 ? 'turno' : 'turnos'}
        </span>
      </div>

      {slots.length > 0 ? (
        <div className="mt-3 grid grid-cols-3 gap-2">
          {slots.map((slot) => {
            const selected = isSlotSelected(slot, court.courtId, selectedSlot);

            return (
              <button
                key={`${court.courtId}-${slot.startTime}`}
                type="button"
                onClick={() =>
                  onSelectSlot({
                    courtId: court.courtId,
                    courtName: court.courtName,
                    sportId: court.sport.id,
                    sportName: court.sport.name,
                    startTime: slot.startTime,
                    endTime: slot.endTime,
                    price: slot.price,
                  })
                }
                aria-pressed={selected}
                className={`flex h-12 items-center justify-center gap-1.5 rounded-md border text-sm font-bold transition ${
                  selected
                    ? 'border-emerald-300 bg-emerald-400 text-[#061019] shadow-lg shadow-emerald-500/20'
                    : 'border-emerald-500/45 bg-emerald-500/12 text-white active:bg-emerald-500/24'
                }`}
              >
                {selected && <Check className="size-4" />}
                {slot.startTime}
              </button>
            );
          })}
        </div>
      ) : (
        <p className="mt-3 rounded-md border border-white/10 bg-white/[0.035] p-3 text-sm text-white/62">
          No hay horarios disponibles para esta cancha.
        </p>
      )}
    </div>
  );
}

function Feature({ icon, title, text }: { icon: React.ReactNode; title: string; text: string }) {
  return (
    <div className="flex gap-4">
      <div className="mt-1 text-emerald-400">{icon}</div>
      <div>
        <p className="text-sm font-medium">{title}</p>
        <p className="mt-1 text-xs text-white/52">{text}</p>
      </div>
    </div>
  );
}

function CourtHeader({
  court,
  complexName,
  compact = false,
}: {
  court?: PublicAvailabilityCourt;
  complexName?: string;
  compact?: boolean;
}) {
  return (
    <div className="flex min-w-0 items-center gap-4">
      {!compact && (
        <div className="flex size-10 items-center justify-center rounded-full border border-white/70">
          <CourtIcon className="size-6 text-white" />
        </div>
      )}
      <div className="min-w-0">
        <h2
          className={compact ? 'truncate text-lg font-semibold' : 'truncate text-xl font-semibold'}
        >
          {court?.courtName ?? 'Cancha'}
        </h2>
        <p className="mt-1 truncate text-sm text-white/62">
          {court?.sport.name ?? 'Deporte'} {complexName ? ` · ${complexName}` : ''}
        </p>
      </div>
    </div>
  );
}

function BookingTimeline({
  court,
  selectedSlot,
  onSelectSlot,
  compact = false,
}: {
  court?: PublicAvailabilityCourt;
  selectedSlot: SelectedSlot | null;
  onSelectSlot: (slot: SelectedSlot) => void;
  compact?: boolean;
}) {
  const range = getTimelineRange(court);
  const ticks = getHourTicks(range.start, range.end, compact);
  const total = range.end - range.start;
  const slots = court?.availableSlots ?? [];

  return (
    <div
      className={
        compact ? 'overflow-x-auto booking-scrollbar px-0' : 'overflow-x-auto booking-scrollbar'
      }
    >
      <div className={compact ? 'min-w-[540px] px-3' : 'min-w-[780px]'}>
        <div className="relative mt-2 h-6">
          {ticks.map((tick) => (
            <div
              key={tick}
              className="absolute top-0 text-xs text-white"
              style={{ left: `${((tick - range.start) / total) * 100}%` }}
            >
              {toTimeLabel(tick)}
            </div>
          ))}
        </div>
        <div className={compact ? 'relative h-20' : 'relative h-24'}>
          {ticks.map((tick) => (
            <span
              key={tick}
              className="absolute top-0 h-full w-px bg-white/[0.035]"
              style={{ left: `${((tick - range.start) / total) * 100}%` }}
            />
          ))}
          {slots.length > 0 ? (
            slots.map((slot) => {
              const left = ((toMinutes(slot.startTime) - range.start) / total) * 100;
              const width = ((toMinutes(slot.endTime) - toMinutes(slot.startTime)) / total) * 100;
              const selected = court ? isSlotSelected(slot, court.courtId, selectedSlot) : false;

              return (
                <button
                  key={`${court?.courtId}-${slot.startTime}`}
                  type="button"
                  onClick={() => {
                    if (!court) return;
                    onSelectSlot({
                      courtId: court.courtId,
                      courtName: court.courtName,
                      sportId: court.sport.id,
                      sportName: court.sport.name,
                      startTime: slot.startTime,
                      endTime: slot.endTime,
                      price: slot.price,
                    });
                  }}
                  className={`absolute top-7 flex h-12 min-w-[50px] items-center justify-center rounded border text-xs transition ${
                    selected
                      ? 'border-emerald-300 bg-emerald-500/45 text-white shadow-lg shadow-emerald-500/20'
                      : 'border-emerald-500 bg-emerald-500/18 text-white hover:bg-emerald-500/28'
                  }`}
                  style={{ left: `${left}%`, width: `${Math.max(width - 0.6, 5)}%` }}
                >
                  <span className="font-semibold">{slot.startTime}</span>
                </button>
              );
            })
          ) : (
            <div className="absolute inset-x-0 top-12 rounded border border-blue-500/60 bg-blue-500/18 px-4 py-5 text-sm text-white/68">
              Sin horarios disponibles
            </div>
          )}
          {slots.slice(0, compact ? 2 : 3).map((slot, index) => {
            const end = toMinutes(slot.endTime);
            const nextStart = slots[index + 1] ? toMinutes(slots[index + 1].startTime) : end + 60;
            if (nextStart - end < 45 || nextStart > range.end) return null;

            const left = ((end - range.start) / total) * 100;
            const width = ((Math.min(nextStart, range.end) - end) / total) * 100;
            return (
              <div
                key={`${court?.courtId}-blocked-${slot.startTime}`}
                className="absolute top-7 flex h-12 min-w-[44px] items-center justify-center rounded border border-blue-500/70 bg-blue-500/24 text-blue-100"
                style={{ left: `${left}%`, width: `${Math.max(width - 0.8, 4.5)}%` }}
              >
                <Lock className="size-4" />
              </div>
            );
          })}
          {selectedSlot && court?.courtId === selectedSlot.courtId && (
            <div
              className="absolute top-0 flex -translate-x-1/2 flex-col items-center"
              style={{
                left: `${((toMinutes(selectedSlot.startTime) - range.start) / total) * 100}%`,
              }}
            >
              <span className="rounded-md bg-emerald-500 px-2 py-1 text-xs font-bold text-white shadow-lg shadow-emerald-500/25">
                {selectedSlot.startTime}
              </span>
              <span className="h-14 w-px border-l border-dashed border-emerald-400" />
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function SelectedSlotCard(props: BookingShellProps & { compact?: boolean }) {
  const {
    selectedSlot,
    form,
    isCreating,
    createError,
    navigationError,
    onSubmit,
    compact = false,
  } = props;
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting, isValid },
  } = form;
  const customerNameInputRef = useRef<HTMLInputElement | null>(null);
  const customerNameRegistration = register('customerName');

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

    const input = customerNameInputRef.current;
    if (!input) return;

    input.focus();
    input.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }, [selectedSlot]);

  return (
    <Panel className={compact ? 'border-emerald-500/70 p-3' : 'p-6'}>
      <div className={compact ? 'flex gap-3' : 'grid grid-cols-[56px_minmax(0,1fr)] gap-5'}>
        <div className="flex size-12 shrink-0 items-center justify-center rounded-full border border-emerald-500 text-emerald-400">
          <Check className="size-7" />
        </div>
        <div className="min-w-0">
          <h3 className="text-lg font-bold">
            {selectedSlot ? '¡Hora libre!' : 'Elegí un horario'}
          </h3>
          <p className="mt-1 text-sm text-white/66">
            {selectedSlot
              ? `Este horario está disponible para reservar. ${selectedSlot.startTime} - ${selectedSlot.endTime}`
              : 'Seleccioná una cancha y un horario disponible.'}
          </p>
        </div>
      </div>

      {selectedSlot && (
        <form
          className={compact ? 'mt-4 space-y-3' : 'mt-5 grid gap-3'}
          onSubmit={handleSubmit(onSubmit)}
        >
          <div className="flex items-center justify-between gap-3 rounded-md border border-emerald-400/20 bg-emerald-400/[0.06] px-3 py-2.5">
            <span className="text-sm text-white/72">Precio del turno</span>
            <span className="shrink-0 text-base font-bold text-white">
              {formatBookingPrice(selectedSlot.price)}
            </span>
          </div>

          <div className={compact ? 'space-y-3' : 'grid grid-cols-2 gap-3'}>
            <Field data-invalid={Boolean(errors.customerName)}>
              <FieldLabel htmlFor="customerName" className="text-white/76">
                Nombre
              </FieldLabel>
              <Input
                id="customerName"
                placeholder="Ej: Juan Perez"
                aria-invalid={Boolean(errors.customerName)}
                className="border-white/10 bg-white/[0.045] text-white placeholder:text-white/32"
                {...customerNameRegistration}
                ref={(element) => {
                  customerNameRegistration.ref(element);
                  customerNameInputRef.current = element;
                }}
              />
              <FieldError errors={[errors.customerName]} />
            </Field>

            <Field data-invalid={Boolean(errors.customerPhone)}>
              <FieldLabel htmlFor="customerPhone" className="text-white/76">
                Telefono
              </FieldLabel>
              <Input
                id="customerPhone"
                type="tel"
                placeholder="Ej: 3875551234"
                aria-invalid={Boolean(errors.customerPhone)}
                className="border-white/10 bg-white/[0.045] text-white placeholder:text-white/32"
                {...register('customerPhone')}
              />
              <FieldError errors={[errors.customerPhone]} />
            </Field>
          </div>

          <Field data-invalid={Boolean(errors.customerEmail)}>
            <FieldLabel htmlFor="customerEmail" className="text-white/76">
              Email
            </FieldLabel>
            <Input
              id="customerEmail"
              type="email"
              placeholder="Ej: juan@ejemplo.com"
              aria-invalid={Boolean(errors.customerEmail)}
              className="border-white/10 bg-white/[0.045] text-white placeholder:text-white/32"
              {...register('customerEmail')}
            />
            <p className="mt-1 text-xs text-white/40">
              Te enviaremos la confirmación de la reserva por email.
            </p>
            <FieldError errors={[errors.customerEmail]} />
          </Field>

          {createError && <p className="text-sm text-red-300">{createError}</p>}
          {navigationError && <p className="text-sm text-red-300">{navigationError}</p>}

          <Button
            type="submit"
            className="h-12 w-full rounded-md bg-emerald-500 font-bold text-white shadow-lg shadow-emerald-500/20 hover:bg-emerald-400"
            disabled={!isValid || isSubmitting || isCreating}
          >
            {isCreating ? 'Confirmando...' : 'Reservar este horario'}
            <ArrowRight className="ml-2 size-5" />
          </Button>
        </form>
      )}
    </Panel>
  );
}

export function PublicBookingPage({ complexSlug }: PublicBookingPageProps) {
  const navigate = useNavigate();
  const isMobile = useIsMobile();
  const today = useMemo(() => new Date(), []);
  const todayIsoDate = useMemo(() => toIsoDateLocal(today), [today]);
  const [windowStartOffset, setWindowStartOffset] = useState(0);
  const [selectedSportId, setSelectedSportId] = useState<string | undefined>();
  const [selectedCourtId, setSelectedCourtId] = useState<string | undefined>();
  const [selectedSlot, setSelectedSlot] = useState<SelectedSlot | null>(null);
  const [navigationError, setNavigationError] = useState<string | null>(null);
  const [hasAutoAdjustedInitialDate, setHasAutoAdjustedInitialDate] = useState(false);
  const [autoAdjustedDateNotice, setAutoAdjustedDateNotice] = useState<string | null>(null);
  const daysToShow = isMobile ? DAYS_STEP : DESKTOP_DAYS_STEP;

  const dayOptions = useMemo(() => {
    return Array.from({ length: daysToShow }).map((_, index) => {
      const current = addDays(today, windowStartOffset + index);
      return formatDayLabel(current);
    });
  }, [daysToShow, today, windowStartOffset]);

  const [selectedDate, setSelectedDate] = useState(dayOptions[0]?.value ?? '');

  useEffect(() => {
    if (dayOptions.some((option) => option.value === selectedDate)) {
      return;
    }

    setSelectedDate(dayOptions[0]?.value ?? '');
  }, [dayOptions, selectedDate]);

  const availabilityQuery = useQuery({
    queryKey: ['public-booking-availability', complexSlug, selectedDate, selectedSportId],
    enabled: Boolean(selectedDate),
    placeholderData: (previousData) => previousData,
    queryFn: () =>
      apiClient.publicBookings.getAvailability(complexSlug, {
        date: selectedDate,
        ...(selectedSportId ? { sportId: selectedSportId } : {}),
      }),
  });

  useEffect(() => {
    const availability = availabilityQuery.data;
    if (!availability) return;

    if (!availability.sportSelectionRequired) {
      if (!selectedSportId && availability.sports[0]) {
        setSelectedSportId(availability.sports[0].id);
      }
      return;
    }

    if (selectedSportId && !availability.sports.some((sport) => sport.id === selectedSportId)) {
      setSelectedSportId(undefined);
    }
  }, [availabilityQuery.data, selectedSportId]);

  const form = useForm<BookingFormValues>({
    resolver: zodResolver(bookingFormSchema),
    mode: 'onChange',
    defaultValues: {
      customerName: '',
      customerPhone: '',
      customerEmail: '',
    },
  });

  const createBookingMutation = useMutation({
    mutationFn: (payload: BookingFormValues) => {
      if (!selectedSlot) {
        throw new Error('Debés seleccionar un horario.');
      }

      return apiClient.publicBookings.create(complexSlug, {
        date: selectedDate,
        sportId: selectedSportId ?? selectedSlot.sportId,
        courtId: selectedSlot.courtId,
        startTime: selectedSlot.startTime,
        customerName: payload.customerName,
        customerPhone: payload.customerPhone,
        customerEmail: payload.customerEmail,
      });
    },
  });

  const onSubmit = async (values: BookingFormValues) => {
    setNavigationError(null);
    const booking = await createBookingMutation.mutateAsync(values);

    if (!booking.bookingCode) {
      setNavigationError('No se pudo obtener el código de confirmación de la reserva.');
      return;
    }

    form.reset();
    setSelectedSlot(null);
    await availabilityQuery.refetch();
    await navigate({
      to: '/$complexSlug/booking/confirmed/$bookingCode',
      params: {
        complexSlug,
        bookingCode: booking.bookingCode,
      },
    });
  };

  const visibleCourts = useMemo(() => {
    const courts = availabilityQuery.data?.courts ?? [];

    if (selectedDate !== todayIsoDate) {
      return courts;
    }

    const now = new Date();
    const nowMinutes = now.getHours() * 60 + now.getMinutes();

    return courts
      .map((court) => ({
        ...court,
        availableSlots: court.availableSlots.filter(
          (slot) => toMinutes(slot.startTime) >= nowMinutes
        ),
      }))
      .filter((court) => court.availableSlots.length > 0);
  }, [availabilityQuery.data?.courts, selectedDate, todayIsoDate]);

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

    const stillAvailable = visibleCourts.some(
      (court) =>
        court.courtId === selectedSlot.courtId &&
        court.availableSlots.some((slot) => slot.startTime === selectedSlot.startTime)
    );

    if (!stillAvailable) {
      setSelectedSlot(null);
    }
  }, [selectedSlot, visibleCourts]);

  useEffect(() => {
    if (visibleCourts.length === 0) {
      setSelectedCourtId(undefined);
      return;
    }

    if (selectedCourtId && visibleCourts.some((court) => court.courtId === selectedCourtId)) {
      return;
    }

    setSelectedCourtId(visibleCourts[0]?.courtId);
  }, [selectedCourtId, visibleCourts]);

  useEffect(() => {
    if (hasAutoAdjustedInitialDate) return;
    if (selectedDate !== todayIsoDate) {
      setHasAutoAdjustedInitialDate(true);
      return;
    }
    if (availabilityQuery.isLoading || availabilityQuery.isError || !availabilityQuery.data) {
      return;
    }

    if (visibleCourts.length > 0) {
      setHasAutoAdjustedInitialDate(true);
      return;
    }

    let cancelled = false;

    const findNextAvailableDate = async () => {
      for (let dayOffset = 1; dayOffset <= 30; dayOffset += 1) {
        const candidateDate = toIsoDateLocal(addDays(today, dayOffset));

        try {
          const availability = await apiClient.publicBookings.getAvailability(complexSlug, {
            date: candidateDate,
            ...(selectedSportId ? { sportId: selectedSportId } : {}),
          });

          if (availability.courts.length === 0) {
            continue;
          }

          if (cancelled) return;

          setWindowStartOffset(dayOffset);
          setSelectedDate(candidateDate);
          setSelectedSlot(null);
          setAutoAdjustedDateNotice(
            'No había turnos disponibles para hoy. Te mostramos el próximo día con turnos.'
          );
          setHasAutoAdjustedInitialDate(true);
          return;
        } catch {
          break;
        }
      }

      if (!cancelled) {
        setHasAutoAdjustedInitialDate(true);
      }
    };

    void findNextAvailableDate();

    return () => {
      cancelled = true;
    };
  }, [
    availabilityQuery.data,
    availabilityQuery.isError,
    availabilityQuery.isLoading,
    complexSlug,
    hasAutoAdjustedInitialDate,
    selectedDate,
    selectedSportId,
    today,
    todayIsoDate,
    visibleCourts.length,
  ]);

  const currentDayIndex = dayOptions.findIndex((option) => option.value === selectedDate);
  const canGoBack = windowStartOffset > 0 || currentDayIndex > 0;

  const selectDate = (date: string) => {
    setSelectedDate(date);
    setSelectedSlot(null);
    setAutoAdjustedDateNotice(null);
  };

  const goToPreviousDays = () => {
    if (!canGoBack) return;

    setWindowStartOffset((previous) => {
      const next = Math.max(0, previous - daysToShow);
      const nextStartDate = toIsoDateLocal(addDays(today, next));
      setSelectedDate(nextStartDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return next;
    });
  };

  const goToNextDays = () => {
    setWindowStartOffset((previous) => {
      const next = previous + daysToShow;
      const nextStartDate = toIsoDateLocal(addDays(today, next));
      setSelectedDate(nextStartDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return next;
    });
  };

  const goToPreviousDay = () => {
    const currentIndex = dayOptions.findIndex((o) => o.value === selectedDate);
    if (currentIndex > 0) {
      const prevDate = dayOptions[currentIndex - 1].value;
      setSelectedDate(prevDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return;
    }
    if (windowStartOffset > 0) {
      const newOffset = windowStartOffset - 1;
      const newStartDate = toIsoDateLocal(addDays(today, newOffset));
      setWindowStartOffset(newOffset);
      setSelectedDate(newStartDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
    }
  };

  const goToNextDay = () => {
    const currentIndex = dayOptions.findIndex((o) => o.value === selectedDate);
    if (currentIndex < dayOptions.length - 1) {
      const nextDate = dayOptions[currentIndex + 1].value;
      setSelectedDate(nextDate);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
      return;
    }
    const newOffset = windowStartOffset + 1;
    const nextDate = toIsoDateLocal(addDays(today, newOffset));
    setWindowStartOffset(newOffset);
    setSelectedDate(nextDate);
    setSelectedSlot(null);
    setAutoAdjustedDateNotice(null);
  };

  const shellProps: BookingShellProps = {
    complexName: availabilityQuery.data?.complexName,
    complexAddress: availabilityQuery.data?.complexAddress,
    sports: availabilityQuery.data?.sports ?? [],
    courts: visibleCourts,
    selectedDate,
    selectedSportId,
    selectedCourtId,
    selectedSlot,
    dayOptions,
    canGoBack,
    isLoading: availabilityQuery.isLoading,
    isError: availabilityQuery.isError,
    sportSelectionRequired: availabilityQuery.data?.sportSelectionRequired,
    errorMessage: availabilityQuery.isError
      ? extractMessage(availabilityQuery.error, 'No pudimos cargar la disponibilidad.')
      : undefined,
    autoAdjustedDateNotice,
    form,
    isCreating: createBookingMutation.isPending,
    createError: createBookingMutation.isError
      ? extractMessage(createBookingMutation.error, 'No pudimos confirmar el turno.')
      : undefined,
    navigationError,
    onSelectDate: selectDate,
    onPreviousDays: goToPreviousDays,
    onNextDays: goToNextDays,
    onPreviousDay: goToPreviousDay,
    onNextDay: goToNextDay,
    onSelectSport: (sportId) => {
      setSelectedSportId(sportId);
      setSelectedCourtId(undefined);
      setSelectedSlot(null);
      setAutoAdjustedDateNotice(null);
    },
    onSelectCourt: (courtId) => {
      setSelectedCourtId(courtId);
      setSelectedSlot(null);
    },
    onSelectSlot: (slot) => {
      setSelectedCourtId(slot.courtId);
      setSelectedSlot(slot);
    },
    onSubmit,
  };

  return isMobile ? (
    <PublicBookingMobile {...shellProps} />
  ) : (
    <PublicBookingDesktop {...shellProps} />
  );
}
