'use client';

import { useMemo, useState } from 'react';
import { Check, ChevronsUpDown } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';

import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { useDebounce } from '@/hooks/use-debounce';
import { apiFetch } from '@/lib/api/client';
import { cn } from '@/lib/utils';

interface BerthOption {
  id: string;
  mooringNumber: string;
  area: string | null;
  status: string;
}

/**
 * Group berth options by area letter extracted from the canonical mooring
 * format `^[A-Z]+\d+$` (A1, B12, etc). Falls back to a single bucket
 * keyed by empty string when no letter is present so callers still see
 * every row. Sorts by area letter then natural-numeric within each group
 * so A1, A2, A10 reads in human order rather than lexicographic.
 */
export function groupOptionsByArea(options: BerthOption[]): [string, BerthOption[]][] {
  const map = new Map<string, BerthOption[]>();
  for (const o of options) {
    const m = o.mooringNumber.match(/^([A-Z]+)/);
    const key = m?.[1] ?? '';
    const bucket = map.get(key) ?? [];
    bucket.push(o);
    map.set(key, bucket);
  }
  // Natural sort within bucket: split letter prefix from number suffix.
  const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
  for (const bucket of map.values()) {
    bucket.sort((a, b) => collator.compare(a.mooringNumber, b.mooringNumber));
  }
  return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
}

interface BerthPickerProps {
  value: string | null;
  onChange: (berthId: string | null) => void;
  /** When set, the dropdown is scoped to berths linked through any of
   *  this client's interests (via interest_berths.primary). Other berths
   *  are hidden so the picker mirrors the relationship the rep is
   *  already building. */
  clientId?: string | null;
  placeholder?: string;
  disabled?: boolean;
}

/**
 * Searchable berth picker. Free-text search when no client is selected;
 * scoped to a client's primary-berth set when `clientId` is provided.
 *
 * The scoped query fetches the client's interests (limit 25) and
 * intersects on `berthId`, which mirrors the relationship semantics the
 * rest of the CRM uses ("berths that show up on this client's deals").
 */
export function BerthPicker({
  value,
  onChange,
  clientId,
  placeholder = 'Select berth...',
  disabled,
}: BerthPickerProps) {
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');
  const debounced = useDebounce(search, 300);

  // Free-text search path - used when there's no clientId scope.
  const { data: searchData } = useQuery<{ data: BerthOption[] }>({
    queryKey: ['berth-picker', 'search', debounced],
    queryFn: () => {
      const params = new URLSearchParams({ page: '1', limit: '10', order: 'asc' });
      // The list endpoint doesn't accept `search`, so we filter
      // client-side; pulling a larger page lets the typeahead feel
      // responsive without round-tripping per keystroke.
      params.set('limit', '50');
      return apiFetch(`/api/v1/berths?${params.toString()}`);
    },
    enabled: open && !clientId,
  });

  // Scoped path - pull this client's interests (with their primary
  // berth) and dedupe the berth set.
  const { data: clientInterests } = useQuery<{
    data: Array<{ berthId: string | null; berthMooringNumber: string | null }>;
  }>({
    queryKey: ['berth-picker', 'client', clientId],
    queryFn: () => {
      const params = new URLSearchParams({
        page: '1',
        limit: '25',
        order: 'desc',
        includeArchived: 'false',
        clientId: clientId!,
      });
      return apiFetch(`/api/v1/interests?${params.toString()}`);
    },
    enabled: open && !!clientId,
  });

  const options: BerthOption[] = useMemo(() => {
    if (clientId) {
      const rows = clientInterests?.data ?? [];
      const seen = new Set<string>();
      const out: BerthOption[] = [];
      for (const r of rows) {
        if (!r.berthId || seen.has(r.berthId)) continue;
        seen.add(r.berthId);
        out.push({
          id: r.berthId,
          mooringNumber: r.berthMooringNumber ?? '',
          area: null,
          status: '',
        });
      }
      if (!debounced) return out;
      const q = debounced.toLowerCase();
      return out.filter((b) => b.mooringNumber.toLowerCase().includes(q));
    }
    const rows = searchData?.data ?? [];
    if (!debounced) return rows;
    const q = debounced.toLowerCase();
    return rows.filter((b) => b.mooringNumber.toLowerCase().includes(q));
  }, [clientId, clientInterests, searchData, debounced]);

  // Mooring numbers already carry the area letter as their prefix
  // (canonical form `^[A-Z]+\d+$` per CLAUDE.md), and the option list
  // is grouped under the area letter heading. Repeating " · A" after
  // "Berth A1" reads as noise — drop the area suffix from the row
  // label. The grouping heading still conveys the same info.
  const labelFor = (o: BerthOption) => `Berth ${o.mooringNumber}`;

  // Group helper outside render so memoization works; takes/returns plain
  // values so the same logic plugs into linked-berths and recommender pickers later.

  const selectedLabel = (() => {
    if (!value) return placeholder;
    const match = options.find((o) => o.id === value);
    return match ? labelFor(match) : `Berth ${value.slice(0, 8)}`;
  })();

  return (
    // `modal` is required when this picker is rendered inside a Sheet /
    // Dialog - without it the CommandInput stays focus-blocked by the
    // outer Sheet's focus trap and clicks/typing are silently dropped.
    <Popover open={open} onOpenChange={setOpen} modal>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          disabled={disabled}
          className={cn('w-full justify-between', !value && 'text-muted-foreground')}
        >
          <span className="truncate">{selectedLabel}</span>
          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[320px] p-0" align="start">
        <Command shouldFilter={false}>
          <CommandInput
            placeholder={clientId ? "Search this client's berths…" : 'Search berths…'}
            value={search}
            onValueChange={setSearch}
          />
          <CommandList>
            <CommandEmpty>
              {clientId ? 'No berths linked to this client.' : 'No berths found.'}
            </CommandEmpty>
            {value ? (
              <CommandGroup>
                <CommandItem
                  value="__clear__"
                  onSelect={() => {
                    onChange(null);
                    setOpen(false);
                  }}
                  className="text-muted-foreground"
                >
                  Clear selection
                </CommandItem>
              </CommandGroup>
            ) : null}
            {groupOptionsByArea(options).map(([letter, group]) => (
              <CommandGroup key={letter || '_'} heading={letter || 'Other'}>
                {group.map((o) => (
                  <CommandItem
                    key={o.id}
                    value={o.id}
                    onSelect={() => {
                      onChange(o.id);
                      setOpen(false);
                    }}
                  >
                    <Check
                      className={cn('mr-2 h-4 w-4', value === o.id ? 'opacity-100' : 'opacity-0')}
                    />
                    <span className="truncate">{labelFor(o)}</span>
                  </CommandItem>
                ))}
              </CommandGroup>
            ))}
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
