import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Group, Select, Text, TextInput, type ComboboxItem, type SelectProps } from '@mantine/core';
import {
  getCountries,
  getCountryCallingCode,
  parsePhoneNumberFromString,
  type CountryCode,
} from 'libphonenumber-js';
import { CountryFlag, getCountryName, resolveLang } from './CountrySelect';
import { exceedsMaxLength, formatNational, maxNationalLength, nextNationalDigits, toE164, toNationalDigits } from './phone';

// Static list, computed once at module load — same as CountrySelect's dataset.
const COUNTRY_CODES = getCountries();

type CountryOption = ComboboxItem & { name: string };

// Search by country name ("united"), dial code ("+1") or ISO prefix ("us") —
// the closed control's label alone ("+1") isn't enough to find a country.
const filterCountries: SelectProps['filter'] = ({ options, search }) => {
  const q = search.trim().toLowerCase();
  if (!q) return options;
  return (options as CountryOption[]).filter(
    (o) =>
      o.name.toLowerCase().includes(q) ||
      o.label.toLowerCase().includes(q) ||
      o.value.toLowerCase().startsWith(q),
  );
};

const renderCountryOption: SelectProps['renderOption'] = ({ option }) => {
  const o = option as CountryOption;
  return (
    <Group gap="xs" wrap="nowrap" justify="space-between" flex={1}>
      <Group gap="xs" wrap="nowrap">
        <CountryFlag code={o.value} />
        <Text fz="sm">{o.name}</Text>
      </Group>
      <Text fz="xs" c="dimmed">{o.label}</Text>
    </Group>
  );
};

export interface PhoneInputProps {
  /** E.164 (`+14155552671`), or '' when empty. */
  value: string;
  onChange: (value: string) => void;
  /**
   * Fired when focus leaves a field that has digits in it — wire to the
   * form's `trigger`. A blank field is left to submit-time validation, like
   * the form's other inputs, so tabbing past it doesn't raise an error.
   */
  onBlur?: () => void;
  label?: React.ReactNode;
  placeholder?: string;
  description?: React.ReactNode;
  error?: React.ReactNode;
  required?: boolean;
  /** Mantine's asterisk-without-`required` variant, as used by config-driven forms. */
  withAsterisk?: boolean;
  disabled?: boolean;
  readOnly?: boolean;
}

/**
 * International phone entry: a searchable country/dial-code select beside a
 * national-number text box, WhatsApp/Telegram style. Controlled —
 * `value`/`onChange` carry the E.164 string.
 *
 * The typed digits live in local state rather than being re-derived from
 * `value` on every render: an incomplete number doesn't parse, so deriving
 * would blank the box between keystrokes.
 */
export function PhoneInput({
  value,
  onChange,
  onBlur,
  label,
  placeholder,
  description,
  error,
  required,
  withAsterisk,
  disabled,
  readOnly,
}: PhoneInputProps) {
  const { t, i18n } = useTranslation();
  const lang = resolveLang(i18n.language);

  const initialCountry = parsePhoneNumberFromString(value || '')?.country ?? 'ET';
  const [country, setCountry] = useState<CountryCode>(initialCountry);
  const [national, setNational] = useState(() =>
    formatNational(toNationalDigits(value, initialCountry), initialCountry),
  );
  // Mantine keeps the selected label ("+251") as the search text, so typing
  // would search for "+251u". Cleared while the dropdown is open instead.
  const [search, setSearch] = useState('');

  // What this field last pushed upward, so an echo of our own value isn't
  // mistaken for the parent resetting the form.
  const emitted = useRef(value);

  useEffect(() => {
    if (value === emitted.current) return;
    const parsedCountry = parsePhoneNumberFromString(value || '')?.country;
    const next = parsedCountry ?? country;
    if (parsedCountry) setCountry(parsedCountry);
    setNational(formatNational(toNationalDigits(value, next), next));
    emitted.current = value;
    // Adopting an outside change only — `country` is state written here, so
    // re-running on it would fight the user's typing.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value]);

  const countryOptions = useMemo<CountryOption[]>(
    () =>
      COUNTRY_CODES.map((code) => ({
        value: code,
        label: `+${getCountryCallingCode(code)}`,
        name: getCountryName(code, lang),
      })).sort((a, b) => a.name.localeCompare(b.name, lang)),
    [lang],
  );

  function push(next: string) {
    emitted.current = next;
    onChange(next);
  }

  function applyDigits(digits: string, forCountry: CountryCode) {
    // Keystroke past the longest possible number is ignored, not stored.
    if (exceedsMaxLength(digits, forCountry)) return;
    // Once the number is valid, show the true national part: someone who
    // types a trunk prefix (0911111111) or the country code (251911111111)
    // shouldn't end up with it doubled beside the "+251" selector. Not
    // before: a partial number can parse too, and rewriting it mid-typing
    // makes digits vanish under the caret.
    const parsed = parsePhoneNumberFromString(digits, forCountry);
    setNational(formatNational(parsed?.isValid() ? parsed.nationalNumber : digits, forCountry));
    push(toE164(digits, forCountry));
  }

  function handleText(text: string) {
    // A full `+<code><number>` arriving at once (paste, autofill, or a test
    // driver's `.fill()`) is parsed standalone and switches the country,
    // rather than being read under whatever country was already selected.
    const trimmed = text.trim();
    if (trimmed.startsWith('+')) {
      const parsed = parsePhoneNumberFromString(trimmed);
      if (parsed?.country) {
        setCountry(parsed.country);
        setNational(formatNational(parsed.nationalNumber, parsed.country));
        push(parsed.number);
        return;
      }
    }
    applyDigits(nextNationalDigits(text, national), country);
  }

  function handleCountryChange(next: string | null) {
    if (!next) return;
    const code = next as CountryCode;
    setCountry(code);
    // A number carried over from a longer plan is cut to fit the new one.
    applyDigits(national.replace(/\D/g, '').slice(0, maxNationalLength(code)), code);
  }

  return (
    <TextInput
      type="tel"
      inputMode="tel"
      autoComplete="tel"
      label={label}
      description={description}
      error={error}
      required={required}
      withAsterisk={withAsterisk}
      disabled={disabled}
      readOnly={readOnly}
      placeholder={placeholder}
      value={national}
      onChange={(e) => handleText(e.currentTarget.value)}
      // Blur of the whole field, not of the number box: moving into the
      // country select mustn't validate a half-typed number.
      wrapperProps={{
        onBlur: (e: React.FocusEvent<HTMLDivElement>) => {
          if (national && !e.currentTarget.contains(e.relatedTarget)) onBlur?.();
        },
      }}
      leftSectionWidth={92}
      leftSectionPointerEvents={disabled || readOnly ? 'none' : 'all'}
      leftSection={
        <Select
          aria-label={t('phone.countryCode', 'Country code')}
          data={countryOptions}
          value={country}
          onChange={handleCountryChange}
          renderOption={renderCountryOption}
          filter={filterCountries}
          searchable
          searchValue={search}
          onSearchChange={setSearch}
          onDropdownOpen={() => setSearch('')}
          onDropdownClose={() => setSearch(`+${getCountryCallingCode(country)}`)}
          nothingFoundMessage={t('phone.noCountry', 'No matching country')}
          allowDeselect={false}
          disabled={disabled || readOnly}
          variant="unstyled"
          size="xs"
          w={92}
          maxDropdownHeight={320}
          comboboxProps={{ width: 260, position: 'bottom-start' }}
          leftSection={<CountryFlag code={country} />}
          leftSectionWidth={30}
        />
      }
    />
  );
}
