// @design-system: domain/CitySelectField
/**
 * CitySelectField — Israeli city autocomplete bound to /api/address/cities,
 * with an "Autodetect" button that calls navigator.geolocation only on user
 * click (no cold permission prompt). On success, POSTs to
 * /api/address/reverse-geocode and autofills the field. Failure → InlineNotice.
 *
 * Value shape is the canonical pair { city, cityCode } from the data.gov.il
 * dataset. Free-text submission is prevented — parent should require a code.
 *
 * @example
 * <CitySelectField value={cityValue} onChange={setCityValue} id="city" />
 */

'use client';

import { useEffect, useMemo, useRef, useState } from 'react';
import { useT } from '@/lib/i18n/react';
import { Label } from '@/components/ui/primitives/Label';
import { Input } from '@/components/ui/primitives/Input';
import { Button } from '@/components/ui/primitives/Button';
import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/overlays/Popover';
import { InlineNotice } from '@/components/ui/feedback/InlineNotice';
import { Icon } from '@/components/ui/icons/Icon';
import { getCsrfToken } from '@/lib/csrf';
import { captureCaught } from '@/lib/observability';
import { citySelectFieldVariants } from './variants';
import { CITY_TIER } from './cityTiers';
import { type GovCity, govCityCode, govCityName } from '../IsraeliAddressField/govTypes';

// ─── Types ────────────────────────────────────────────────────────────────────

export interface CityValue {
  city: string;
  cityCode: string;
}

export interface CitySelectFieldProps {
  value: CityValue | null;
  onChange: (next: CityValue | null) => void;
  id?: string;
}

interface CityOption {
  code: string;
  name: string;
}

type DetectState = 'idle' | 'pending' | 'error';


// ─── CityCombobox ─────────────────────────────────────────────────────────────

interface CityComboboxProps {
  id?: string;
  items: CityOption[];
  value: CityOption | null;
  onChange: (code: string) => void;
  placeholder: string;
  loading?: boolean;
}

function CityCombobox({ id, items, value, onChange, placeholder, loading }: CityComboboxProps) {
  const listId = `${id ?? 'city'}-list`;

  const [open, setOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(-1);
  const [dirty, setDirty] = useState(false);
  const [inputValue, setInputValue] = useState(value?.name ?? '');

  // Sync input when value changes externally (e.g., autodetect autofills).
  const prevCode = useRef<string | undefined>(value?.code);
  if (prevCode.current !== value?.code) {
    prevCode.current = value?.code;
    setInputValue(value?.name ?? '');
    setDirty(false);
  }

  const filtered = useMemo<CityOption[]>(() => {
    if (!dirty) return items;
    const q = inputValue.trim().toLowerCase();
    if (!q) return items;
    const matches = items.filter((item) => item.name.toLowerCase().includes(q));
    matches.sort((a, b) => {
      const scoreA = (a.name.toLowerCase().startsWith(q) ? 100 : 0) + (CITY_TIER.get(a.code) ?? 0);
      const scoreB = (b.name.toLowerCase().startsWith(q) ? 100 : 0) + (CITY_TIER.get(b.code) ?? 0);
      return scoreB - scoreA;
    });
    return matches;
  }, [items, inputValue, dirty]);

  const selectItem = (item: CityOption) => {
    setInputValue(item.name);
    setDirty(false);
    setOpen(false);
    setActiveIndex(-1);
    onChange(item.code);
  };

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(e.target.value);
    setDirty(true);
    setActiveIndex(-1);
    if (!open) setOpen(true);
  };

  const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
    setDirty(false);
    setOpen(true);
    e.target.select();
  };

  const handleBlur = () => {
    // Reset to last confirmed value — prevents free-text submission.
    setInputValue(value?.name ?? '');
    setDirty(false);
    setOpen(false);
    setActiveIndex(-1);
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (!open) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
        e.preventDefault();
        setOpen(true);
      }
      return;
    }
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setActiveIndex((i) => Math.max(i - 1, 0));
    } else if (e.key === 'Enter') {
      e.preventDefault();
      if (activeIndex >= 0 && filtered[activeIndex]) {
        selectItem(filtered[activeIndex]);
      }
    } else if (e.key === 'Escape') {
      setOpen(false);
      setActiveIndex(-1);
    }
  };

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverAnchor asChild>
        <Input
          id={id}
          role="combobox"
          aria-expanded={open}
          aria-haspopup="listbox"
          aria-autocomplete="list"
          aria-controls={listId}
          aria-activedescendant={
            activeIndex >= 0 ? `${id ?? 'city'}-option-${activeIndex}` : undefined
          }
          value={inputValue}
          onChange={handleInputChange}
          onFocus={handleFocus}
          onBlur={handleBlur}
          onKeyDown={handleKeyDown}
          placeholder={loading ? '...' : placeholder}
          disabled={loading}
          autoComplete="off"
        />
      </PopoverAnchor>
      {open && (
        <PopoverContent
          align="start"
          sideOffset={4}
          onOpenAutoFocus={(e) => e.preventDefault()}
          className="max-h-60 w-[var(--radix-popover-trigger-width)] overflow-y-auto p-0"
        >
          {filtered.length === 0 ? (
            <div role="status" className="text-text-secondary px-3 py-2 text-sm">
              —
            </div>
          ) : (
            <ul id={listId} role="listbox" className="py-1">
              {filtered.map((item, i) => (
                <li
                  key={item.code}
                  id={`${id ?? 'city'}-option-${i}`}
                  role="option"
                  aria-selected={item.code === value?.code}
                  className={
                    'cursor-pointer px-3 py-2 text-sm ' +
                    (i === activeIndex
                      ? 'bg-brand-primary-50 text-brand-primary-700'
                      : 'text-text-primary hover:bg-surface-raised')
                  }
                  onMouseDown={(e) => {
                    // Prevent blur firing before click completes.
                    e.preventDefault();
                    selectItem(item);
                  }}
                  onKeyDown={(e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                      e.preventDefault();
                      selectItem(item);
                    }
                  }}
                >
                  {item.name}
                </li>
              ))}
            </ul>
          )}
        </PopoverContent>
      )}
    </Popover>
  );
}

// ─── CitySelectField ──────────────────────────────────────────────────────────

/**
 * CitySelectField
 *
 * Renders a city autocomplete backed by the data.gov.il city dataset,
 * plus an Autodetect button for geolocation-based autofill.
 */
export function CitySelectField({ value, onChange, id = 'city-select' }: CitySelectFieldProps) {
  const t = useT('onboarding');
  const [options, setOptions] = useState<CityOption[]>([]);
  const [detect, setDetect] = useState<DetectState>('idle');

  // Load city list once on mount.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch('/api/address/cities', { credentials: 'include' });
        if (!res.ok) return;
        const data = (await res.json()) as {
          result?: { records?: GovCity[] };
        };
        const records = data.result?.records ?? [];
        if (cancelled) return;
        setOptions(
          records.map((r) => ({ code: govCityCode(r).trim(), name: govCityName(r).trim() })),
        );
      } catch (err) {
        captureCaught(err, { scope: 'ui.cityselectfield.load', severity: 'info' });
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  async function handleAutodetect(): Promise<void> {
    if (typeof navigator === 'undefined' || !navigator.geolocation) {
      setDetect('error');
      return;
    }
    setDetect('pending');

    let coords: GeolocationCoordinates;
    try {
      coords = await new Promise<GeolocationCoordinates>((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(
          (pos) => resolve(pos.coords),
          (err) => reject(err),
          { timeout: 8_000, maximumAge: 0 },
        );
      });
    } catch (err) {
      captureCaught(err, { scope: 'ui.cityselectfield.geolocation', severity: 'info' });
      setDetect('error');
      return;
    }

    try {
      const res = await fetch('/api/address/reverse-geocode', {
        method: 'POST',
        credentials: 'include',
        headers: {
          'Content-Type': 'application/json',
          'x-csrf-token': getCsrfToken(),
        },
        // NOTE: lat/lng not logged (PII-adjacent).
        body: JSON.stringify({ lat: coords.latitude, lng: coords.longitude }),
      });
      if (!res.ok) {
        setDetect('error');
        return;
      }
      const data = (await res.json()) as { city: string | null; cityCode: string | null };
      if (data.city && data.cityCode) {
        onChange({ city: data.city, cityCode: data.cityCode });
        setDetect('idle');
        return;
      }
      setDetect('error');
    } catch (err) {
      captureCaught(err, { scope: 'ui.cityselectfield.autodetect', severity: 'info' });
      setDetect('error');
    }
  }

  const comboboxValue: CityOption | null = value
    ? { code: value.cityCode, name: value.city }
    : null;

  return (
    <div className={citySelectFieldVariants()}>
      <Label htmlFor={id}>{t('city_label')}</Label>
      <CityCombobox
        id={id}
        items={options}
        value={comboboxValue}
        onChange={(code) => {
          const opt = options.find((o) => o.code === code);
          if (opt) {
            onChange({ city: opt.name, cityCode: opt.code });
          } else {
            onChange(null);
          }
        }}
        placeholder={t('city_placeholder')}
        loading={options.length === 0}
      />
      <div className="flex flex-wrap items-center gap-3">
        <Button
          type="button"
          variant="ghost"
          size="sm"
          loading={detect === 'pending'}
          onClick={handleAutodetect}
          aria-label={t('city_autodetect')}
          iconStart={<Icon name="MapPin" size="sm" mirror />}
        >
          {detect === 'pending' ? t('city_autodetect_pending') : t('city_autodetect')}
        </Button>
        <p className="text-text-muted text-sm">{t('city_helper')}</p>
      </div>
      {detect === 'error' && (
        <InlineNotice tone="warning" description={t('city_autodetect_error')} />
      )}
    </div>
  );
}
