import { useEffect, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
  CONSUMING_ALLOCATION_STATUSES,
  GUARANTEE_TYPES,
  RESERVING_ALLOCATION_STATUSES,
} from '@pig/core';
import { AlertTriangle, Clock3, LoaderCircle, RotateCcw, ShieldCheck } from 'lucide-react';
import { useForm, type Control, type FieldPath, type FieldValues } from 'react-hook-form';
import { z } from 'zod';
import { Badge, Button, Input } from '@/components/ui';
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
} from '@/components/ui/sheet';
import { Textarea } from '@/components/ui/textarea';
import { ApiError, compactNumber, get, money, percent, post, shortDate } from '@/lib/api';

export interface AvailabilityRow {
  commitmentId: string;
  name: string;
  gpuType: string;
  gpuCount: number;
  interconnectType: string;
  securityTier: string;
  startsAt: string;
  endsAt: string;
  totalGpuHours: number;
  soldGpuHours: number;
  heldGpuHours: number;
  availableGpuHours: number;
  costPerGpuHourCents: number;
  utilisation: number;
  breakEvenPriceCents: number | null;
}

export type MatchRow = AvailabilityRow & { score: number; rationale: string[] };

interface CommitmentRecord {
  id: string;
  shape: { intervals: string[]; quantities: number[] } | null;
  isContiguous: boolean;
  oversubscriptionPct: string | number;
}

interface CommitmentRow {
  commitment: CommitmentRecord;
  accountName: string | null;
}

interface DealRecord {
  id: string;
  name: string;
  stage: string;
}

interface DemandBoard {
  deals: { deal: DealRecord; accountName: string | null }[];
}

interface AllocationRecord {
  id: string;
  capacityCommitmentId: string;
  demandDealId: string | null;
  gpuHours: string | number;
  pricePerGpuHourCents: number;
  startsAt: string;
  endsAt: string;
  status: string;
  holdExpiresAt: string | null;
  guaranteeType: string;
}

const activeReleaseStatuses = RESERVING_ALLOCATION_STATUSES.filter(
  (status) => status !== 'completed',
);
const createStatuses = CONSUMING_ALLOCATION_STATUSES.filter((status) => status !== 'completed');
const numeric = z.string().refine(
  (value) => Number.isFinite(Number(value)) && Number(value) > 0,
  'Enter a number greater than zero.',
);
const moneyValue = z.string().refine(
  (value) => value === '' || (Number.isFinite(Number(value)) && Number(value) >= 0),
  'Enter zero or a positive amount.',
);
const formSchema = z
  .object({
    kind: z.enum(['allocation', 'hold']),
    capacityCommitmentId: z.string().uuid('Select a commitment.'),
    demandDealId: z.string().uuid('Select a demand deal.'),
    gpuHours: numeric,
    price: moneyValue,
    startsAt: z.string().min(1, 'Start is required.'),
    endsAt: z.string().min(1, 'End is required.'),
    holdExpiresAt: z.string(),
    status: z.enum(CONSUMING_ALLOCATION_STATUSES),
    guaranteeType: z.enum(GUARANTEE_TYPES),
    notes: z.string().max(10_000, 'Keep notes under 10,000 characters.'),
  })
  .superRefine((values, context) => {
    const startsAt = new Date(values.startsAt);
    const endsAt = new Date(values.endsAt);
    if (endsAt <= startsAt) {
      context.addIssue({ code: 'custom', path: ['endsAt'], message: 'End must be after start.' });
    }
    if (values.kind === 'allocation' && values.price === '') {
      context.addIssue({ code: 'custom', path: ['price'], message: 'Sell price is required.' });
    }
    if (values.kind === 'hold') {
      const expiresAt = new Date(values.holdExpiresAt);
      if (!values.holdExpiresAt || expiresAt <= new Date()) {
        context.addIssue({
          code: 'custom',
          path: ['holdExpiresAt'],
          message: 'A hold must expire in the future.',
        });
      }
    }
  });
type AllocationForm = z.infer<typeof formSchema>;

function localDateTime(value: string | Date): string {
  const date = new Date(value);
  const offset = date.getTimezoneOffset() * 60_000;
  return new Date(date.getTime() - offset).toISOString().slice(0, 16);
}

function localCommitmentBound(value: string | Date, bound: 'start' | 'end'): string {
  const timestamp = new Date(value).getTime();
  const minute = 60_000;
  const rounded = bound === 'start'
    ? Math.ceil(timestamp / minute) * minute
    : Math.floor(timestamp / minute) * minute;
  return localDateTime(new Date(rounded));
}

function defaults(
  preferredCommitmentId?: string,
  defaultGpuHours?: number,
): AllocationForm {
  return {
    kind: 'allocation',
    capacityCommitmentId: preferredCommitmentId ?? '',
    demandDealId: '',
    gpuHours: defaultGpuHours == null ? '' : String(defaultGpuHours),
    price: '',
    startsAt: '',
    endsAt: '',
    holdExpiresAt: localDateTime(new Date(Date.now() + 24 * 60 * 60 * 1_000)),
    status: 'committed',
    guaranteeType: 'committed',
    notes: '',
  };
}

export function AllocationSheet({
  open,
  onOpenChange,
  preferredCommitmentId,
  matches,
  defaultGpuHours,
  onChanged,
}: {
  open: boolean;
  onOpenChange(open: boolean): void;
  preferredCommitmentId?: string;
  matches?: MatchRow[];
  defaultGpuHours?: number;
  onChanged?(): void;
}) {
  const queryClient = useQueryClient();
  const [releaseReason, setReleaseReason] = useState('');
  const [releaseError, setReleaseError] = useState<string | null>(null);
  const form = useForm<AllocationForm>({
    resolver: zodResolver(formSchema),
    defaultValues: defaults(preferredCommitmentId, defaultGpuHours),
  });
  const { data: availability, isLoading: availabilityLoading } = useQuery({
    queryKey: ['availability'],
    queryFn: () => get<AvailabilityRow[]>('/api/capacity/availability'),
    enabled: open,
  });
  const { data: commitments } = useQuery({
    queryKey: ['commitments', 'allocation-context'],
    queryFn: () => get<CommitmentRow[]>('/api/commitments'),
    enabled: open,
  });
  const { data: demand } = useQuery({
    queryKey: ['/api/deals/demand'],
    queryFn: () => get<DemandBoard>('/api/deals/demand'),
    enabled: open,
  });
  const { data: allocations } = useQuery({
    queryKey: ['allocations'],
    queryFn: () => get<AllocationRecord[]>('/api/allocations'),
    enabled: open,
  });

  useEffect(() => {
    if (!open) return;
    form.reset(defaults(preferredCommitmentId, defaultGpuHours));
    setReleaseReason('');
    setReleaseError(null);
  }, [defaultGpuHours, form, open, preferredCommitmentId]);

  const contextIds = useMemo(
    () => (matches ? new Set(matches.map((match) => match.commitmentId)) : null),
    [matches],
  );
  const options = useMemo(
    () => (availability ?? []).filter((row) => !contextIds || contextIds.has(row.commitmentId)),
    [availability, contextIds],
  );
  const selectedId = form.watch('capacityCommitmentId');
  const selected = options.find((row) => row.commitmentId === selectedId);
  const detail = commitments?.find((row) => row.commitment.id === selectedId);
  const match = matches?.find((row) => row.commitmentId === selectedId);
  const kind = form.watch('kind');
  const quotedPriceValue = form.watch('price');
  const quotedPrice = quotedPriceValue === '' ? null : Number(quotedPriceValue);
  const dealsById = useMemo(
    () => new Map((demand?.deals ?? []).map((row) => [row.deal.id, row])),
    [demand],
  );
  const reserving = useMemo(() => {
    const now = Date.now();
    return (allocations ?? []).filter(
      (allocation) =>
        allocation.capacityCommitmentId === selectedId &&
        activeReleaseStatuses.some((status) => status === allocation.status) &&
        !(
          allocation.status === 'planned' &&
          allocation.holdExpiresAt &&
          new Date(allocation.holdExpiresAt).getTime() <= now
        ),
    );
  }, [allocations, selectedId]);

  useEffect(() => {
    if (!open || !selected || form.getValues('startsAt')) return;
    form.setValue('startsAt', localCommitmentBound(selected.startsAt, 'start'));
    form.setValue('endsAt', localCommitmentBound(selected.endsAt, 'end'));
  }, [form, open, selected]);

  const refresh = async () => {
    await Promise.all([
      queryClient.invalidateQueries({ queryKey: ['availability'] }),
      queryClient.invalidateQueries({ queryKey: ['allocations'] }),
      queryClient.invalidateQueries({ queryKey: ['margin'] }),
      queryClient.invalidateQueries({ queryKey: ['dashboard'] }),
      queryClient.invalidateQueries({ queryKey: ['/api/deals/demand'] }),
    ]);
    onChanged?.();
  };

  const save = useMutation({
    mutationFn: (values: AllocationForm) => {
      const price = values.price === '' ? undefined : Number(values.price);
      const body = {
        capacityCommitmentId: values.capacityCommitmentId,
        demandDealId: values.demandDealId,
        gpuHours: Number(values.gpuHours),
        pricePerGpuHourCents: price === undefined ? undefined : Math.round(price * 100),
        startsAt: new Date(values.startsAt).toISOString(),
        endsAt: new Date(values.endsAt).toISOString(),
        guaranteeType: values.guaranteeType,
        notes: values.notes.trim() || null,
      };
      return values.kind === 'hold'
        ? post<AllocationRecord>('/api/allocations/holds', {
            ...body,
            holdExpiresAt: new Date(values.holdExpiresAt).toISOString(),
          })
        : post<AllocationRecord>('/api/allocations', { ...body, status: values.status });
    },
    onSuccess: async () => {
      await refresh();
      onOpenChange(false);
    },
  });
  const release = useMutation({
    mutationFn: (id: string) =>
      post<AllocationRecord>(`/api/allocations/${id}/release`, {
        reason: releaseReason.trim() || undefined,
      }),
    onMutate: () => setReleaseError(null),
    onSuccess: refresh,
    onError: (error) => setReleaseError(errorMessage(error)),
  });

  const chooseCommitment = (id: string) => {
    form.setValue('capacityCommitmentId', id, { shouldValidate: true });
    const row = options.find((option) => option.commitmentId === id);
    if (row) {
      form.setValue('startsAt', localCommitmentBound(row.startsAt, 'start'), { shouldValidate: true });
      form.setValue('endsAt', localCommitmentBound(row.endsAt, 'end'), { shouldValidate: true });
    }
  };

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent className="flex h-full w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-2xl">
        <SheetHeader className="shrink-0 gap-1 px-5 pb-4 pt-5 text-left sm:px-6">
          <SheetTitle>Reserve capacity</SheetTitle>
          <SheetDescription>
            Join committed supply to a demand deal. Availability is re-checked by the server when you save.
          </SheetDescription>
        </SheetHeader>
        <Separator />

        <Form {...form}>
          <form
            className="flex min-h-0 flex-1 flex-col"
            onSubmit={form.handleSubmit((values) => save.mutate(values))}
          >
            <div className="flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto px-5 py-5 sm:px-6">
              <div className="grid grid-cols-2 rounded-lg bg-surface-2 p-1" role="group" aria-label="Reservation type">
                {(['allocation', 'hold'] as const).map((value) => (
                  <button
                    key={value}
                    type="button"
                    onClick={() => form.setValue('kind', value)}
                    className={
                      kind === value
                        ? 'tap rounded-md bg-surface px-3 text-sm font-medium text-fg shadow-sm'
                        : 'tap rounded-md px-3 text-sm font-medium text-muted'
                    }
                  >
                    {value === 'allocation' ? 'Sell allocation' : 'Timed hold'}
                  </button>
                ))}
              </div>

              <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
                <FormField
                  control={form.control}
                  name="capacityCommitmentId"
                  render={({ field }) => (
                    <FormItem className="sm:col-span-2">
                      <FormLabel>Capacity commitment</FormLabel>
                      <Select value={field.value} onValueChange={chooseCommitment}>
                        <FormControl>
                          <SelectTrigger className="h-11">
                            <SelectValue placeholder={availabilityLoading ? 'Loading capacity…' : 'Select capacity'} />
                          </SelectTrigger>
                        </FormControl>
                        <SelectContent>
                          <SelectGroup>
                            {options.map((row) => (
                              <SelectItem key={row.commitmentId} value={row.commitmentId}>
                                {row.name} · {compactNumber(row.availableGpuHours)} GPU-hrs free
                              </SelectItem>
                            ))}
                          </SelectGroup>
                        </SelectContent>
                      </Select>
                      {matches ? <FormDescription>Limited to the capacity returned by this match.</FormDescription> : null}
                      <FormMessage />
                    </FormItem>
                  )}
                />
                <FormField
                  control={form.control}
                  name="demandDealId"
                  render={({ field }) => (
                    <FormItem className="sm:col-span-2">
                      <FormLabel>Demand deal</FormLabel>
                      <Select value={field.value} onValueChange={field.onChange}>
                        <FormControl>
                          <SelectTrigger className="h-11"><SelectValue placeholder="Select the customer deal" /></SelectTrigger>
                        </FormControl>
                        <SelectContent>
                          <SelectGroup>
                            {(demand?.deals ?? [])
                              .filter((row) => row.deal.stage !== 'closed_lost')
                              .map((row) => (
                                <SelectItem key={row.deal.id} value={row.deal.id}>
                                  {row.deal.name} · {row.accountName ?? 'No account'}
                                </SelectItem>
                              ))}
                          </SelectGroup>
                        </SelectContent>
                      </Select>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>

              {selected ? (
                <CommitmentContext row={selected} detail={detail} match={match} quotedPrice={quotedPrice} />
              ) : options.length === 0 && !availabilityLoading ? (
                <div role="status" className="rounded-lg border border-border bg-surface-2 p-4 text-sm text-muted">
                  No currently available commitment remains in this context. Run the matcher again before promising capacity.
                </div>
              ) : null}

              <section className="flex flex-col gap-4">
                <div>
                  <h3 className="text-sm font-semibold">Commercial reservation</h3>
                  <p className="mt-1 text-xs leading-relaxed text-muted">
                    GPU-hours and the window are submitted to the ledger as entered. The server checks the term, shaped capacity, holds, and concurrent writes.
                  </p>
                </div>
                <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
                  <TextField control={form.control} name="gpuHours" label="GPU-hours" inputMode="decimal" placeholder="2048" />
                  <TextField control={form.control} name="price" label={kind === 'hold' ? 'Expected $/GPU-hr' : 'Sell $/GPU-hr'} inputMode="decimal" placeholder={kind === 'hold' ? 'Optional' : '2.75'} />
                  <TextField control={form.control} name="startsAt" label="Starts" type="datetime-local" />
                  <TextField control={form.control} name="endsAt" label="Ends" type="datetime-local" />
                  {kind === 'hold' ? (
                    <TextField control={form.control} name="holdExpiresAt" label="Hold expires" type="datetime-local" className="sm:col-span-2" />
                  ) : (
                    <SelectField control={form.control} name="status" label="Allocation status" options={createStatuses} />
                  )}
                  <SelectField control={form.control} name="guaranteeType" label="Service guarantee" options={GUARANTEE_TYPES} />
                  <FormField
                    control={form.control}
                    name="notes"
                    render={({ field }) => (
                      <FormItem className="sm:col-span-2">
                        <FormLabel>Reservation notes</FormLabel>
                        <FormControl><Textarea {...field} className="min-h-24 resize-y" placeholder="Commercial assumptions, caveats, or approval context." /></FormControl>
                        <FormMessage />
                      </FormItem>
                    )}
                  />
                </div>
              </section>

              {selected ? (
                <section className="flex flex-col gap-3">
                  <div>
                    <h3 className="text-sm font-semibold">Reservations on this commitment</h3>
                    <p className="mt-1 text-xs text-muted">Live holds reserve capacity but remain separate from sold allocations.</p>
                  </div>
                  {reserving.length === 0 ? (
                    <p className="rounded-lg bg-surface-2 p-4 text-sm text-muted">No live reserving allocations.</p>
                  ) : (
                    <div className="flex flex-col gap-2">
                      {reserving.map((allocation) => {
                        const deal = allocation.demandDealId ? dealsById.get(allocation.demandDealId) : undefined;
                        return (
                          <div key={allocation.id} className="flex flex-col gap-3 rounded-lg border border-border p-3 sm:flex-row sm:items-center sm:justify-between">
                            <div className="min-w-0">
                              <div className="flex flex-wrap items-center gap-2">
                                <p className="truncate text-sm font-medium">{deal?.deal.name ?? 'Internal allocation'}</p>
                                <Badge tone={allocation.status === 'planned' ? 'warning' : 'positive'}>{allocation.status === 'planned' ? 'Held' : allocation.status}</Badge>
                              </div>
                              <p className="mt-1 text-xs text-muted">
                                {compactNumber(Number(allocation.gpuHours))} GPU-hrs · {shortDate(allocation.startsAt)}–{shortDate(allocation.endsAt)}
                                {allocation.holdExpiresAt ? ` · expires ${shortDate(allocation.holdExpiresAt)}` : ''}
                              </p>
                            </div>
                            <Button type="button" variant="outline" className="shrink-0" disabled={release.isPending} onClick={() => release.mutate(allocation.id)}>
                              {release.isPending && release.variables === allocation.id ? <LoaderCircle data-icon="inline-start" className="animate-spin" aria-hidden /> : <RotateCcw data-icon="inline-start" aria-hidden />}
                              Release
                            </Button>
                          </div>
                        );
                      })}
                      <label className="flex flex-col gap-1 text-xs font-medium text-muted">
                        Release reason <span className="font-normal">Optional; recorded in the audit trail</span>
                        <Input value={releaseReason} onChange={(event) => setReleaseReason(event.target.value)} placeholder="Deal changed, hold lapsed…" />
                      </label>
                    </div>
                  )}
                  {releaseError ? <ServerError message={releaseError} /> : null}
                </section>
              ) : null}

              {save.isError ? <ServerError message={errorMessage(save.error)} /> : null}
            </div>

            <Separator />
            <div className="flex shrink-0 flex-col-reverse gap-2 px-5 pb-[calc(1rem+var(--safe-bottom))] pt-4 sm:flex-row sm:justify-end sm:px-6">
              <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
              <Button type="submit" variant="primary" disabled={save.isPending || options.length === 0}>
                {save.isPending ? <LoaderCircle data-icon="inline-start" className="animate-spin" aria-hidden /> : kind === 'hold' ? <Clock3 data-icon="inline-start" aria-hidden /> : <ShieldCheck data-icon="inline-start" aria-hidden />}
                {save.isPending ? 'Checking capacity…' : kind === 'hold' ? 'Place timed hold' : 'Create allocation'}
              </Button>
            </div>
          </form>
        </Form>
      </SheetContent>
    </Sheet>
  );
}

function CommitmentContext({ row, detail, match, quotedPrice }: { row: AvailabilityRow; detail?: CommitmentRow; match?: MatchRow; quotedPrice: number | null }) {
  const soldPct = row.totalGpuHours > 0 ? row.soldGpuHours / row.totalGpuHours : 0;
  const heldPct = row.totalGpuHours > 0 ? row.heldGpuHours / row.totalGpuHours : 0;
  const breakEvenDollars = row.breakEvenPriceCents == null ? null : row.breakEvenPriceCents / 100;
  const delta = quotedPrice != null && Number.isFinite(quotedPrice) && quotedPrice >= 0 && breakEvenDollars != null
    ? quotedPrice - breakEvenDollars
    : null;
  const shape = detail?.commitment.shape;
  return (
    <section className="rounded-xl border border-border bg-surface-2 p-4">
      <div className="flex flex-wrap items-start justify-between gap-2">
        <div className="min-w-0">
          <p className="truncate font-semibold">{row.name}</p>
          <p className="mt-1 text-xs text-muted">{row.gpuCount}× {row.gpuType} · {row.interconnectType} · {row.securityTier.replace(/_/g, ' ')}</p>
        </div>
        {match ? <Badge tone={match.score > 0.7 ? 'positive' : 'neutral'}>{percent(match.score)} fit</Badge> : null}
      </div>
      <div className="mt-4 flex h-2 overflow-hidden rounded-full bg-surface">
        <div className="bg-accent" style={{ width: `${Math.min(100, soldPct * 100)}%` }} />
        <div className="bg-accent/35" style={{ width: `${Math.min(100 - soldPct * 100, heldPct * 100)}%` }} />
      </div>
      <div className="mt-2 grid grid-cols-3 gap-2 text-xs">
        <div><p className="text-muted">Sold</p><p className="nums mt-0.5 font-medium">{compactNumber(row.soldGpuHours)} hrs</p></div>
        <div><p className="text-muted">Held</p><p className="nums mt-0.5 font-medium">{compactNumber(row.heldGpuHours)} hrs</p></div>
        <div><p className="text-muted">Available</p><p className="nums mt-0.5 font-medium">{compactNumber(row.availableGpuHours)} hrs</p></div>
      </div>
      <Separator className="my-4" />
      <dl className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">
        <dt className="text-muted">Contract window</dt><dd className="text-right">{shortDate(row.startsAt)}–{shortDate(row.endsAt)}</dd>
        <dt className="text-muted">Capacity shape</dt><dd className="text-right">{shape ? `${shape.quantities.length} tranches · ${shape.quantities.join('→')} GPUs` : 'Flat'}{detail?.commitment.isContiguous ? ' · contiguous' : ''}</dd>
        <dt className="text-muted">Our cost</dt><dd className="nums text-right">{money(row.costPerGpuHourCents)}/GPU-hr</dd>
        <dt className="text-muted">Remaining-block break even</dt><dd className="nums text-right">{row.breakEvenPriceCents == null ? 'Fully sold' : row.breakEvenPriceCents === 0 ? 'Cost covered' : `${money(row.breakEvenPriceCents)}/GPU-hr`}</dd>
        {Number(detail?.commitment.oversubscriptionPct ?? 0) > 0 ? <><dt className="text-muted">Recorded oversubscription</dt><dd className="nums text-right">{Number(detail?.commitment.oversubscriptionPct)}%</dd></> : null}
        {delta != null ? <><dt className="text-muted">Quote vs break even</dt><dd className={delta >= 0 ? 'nums text-right text-positive' : 'nums text-right text-danger'}>{delta >= 0 ? '+' : ''}{money(Math.round(delta * 100))}/GPU-hr</dd></> : null}
      </dl>
      {match?.rationale.length ? <ul className="mt-4 flex flex-col gap-1 text-xs text-muted">{match.rationale.map((reason) => <li key={reason}>{reason}</li>)}</ul> : null}
      <p className="mt-4 text-[11px] leading-relaxed text-muted">These figures are the latest server view, not a guarantee. Save acquires a commitment lock and re-checks the exact window, shape, hours, live holds, and oversubscription policy.</p>
    </section>
  );
}

function TextField<T extends FieldValues>({ control, name, label, className, ...props }: { control: Control<T>; name: FieldPath<T>; label: string; className?: string } & Omit<React.ComponentProps<typeof Input>, 'name' | 'value' | 'defaultValue'>) {
  return <FormField control={control} name={name} render={({ field }) => <FormItem className={className}><FormLabel>{label}</FormLabel><FormControl><Input {...field} {...props} value={String(field.value ?? '')} /></FormControl><FormMessage /></FormItem>} />;
}

function SelectField<T extends FieldValues>({ control, name, label, options }: { control: Control<T>; name: FieldPath<T>; label: string; options: readonly string[] }) {
  return <FormField control={control} name={name} render={({ field }) => <FormItem><FormLabel>{label}</FormLabel><Select value={String(field.value)} onValueChange={field.onChange}><FormControl><SelectTrigger className="h-11"><SelectValue /></SelectTrigger></FormControl><SelectContent><SelectGroup>{options.map((option) => <SelectItem key={option} value={option}>{option.replace(/_/g, ' ').replace(/^./, (letter) => letter.toUpperCase())}</SelectItem>)}</SelectGroup></SelectContent></Select><FormMessage /></FormItem>} />;
}

function ServerError({ message }: { message: string }) {
  return <div role="alert" className="flex gap-3 rounded-lg border border-danger/30 bg-danger/10 p-3 text-sm text-danger"><AlertTriangle className="mt-0.5 size-4 shrink-0" aria-hidden /><p>{message}</p></div>;
}

function errorMessage(error: unknown): string {
  if (error instanceof ApiError) return error.message;
  return error instanceof Error ? error.message : 'The reservation could not be saved.';
}
