"use client";

import { Select } from "@/components/FormElements/select";
import { cn } from "@/lib/utils";
import {
  normalizeToE164,
  PHONE_UI_MAX_LENGTH,
  splitE164ForEditing,
  toBestEffortE164,
} from "@/lib/phone/e164";
import {
  DEFAULT_PHONE_COUNTRY,
  PHONE_COUNTRIES,
} from "@/constants/phone-countries";
import { AsYouType, type CountryCode } from "libphonenumber-js";
import {
  useCallback,
  useEffect,
  useId,
  useRef,
  useState,
  type ChangeEvent,
} from "react";
import type { ComponentProps } from "react";
import type { FieldValues } from "react-hook-form";

type CountrySelectOnChange = NonNullable<
  ComponentProps<typeof Select<FieldValues>>["onChange"]
>;

export type PhoneNumberInputProps = {
  label: string;
  /** Stored value in E.164 (e.g. `+14155552671`). */
  value?: string;
  onChange: (e164: string) => void;
  onBlur?: () => void;
  error?: string;
  required?: boolean;
  disabled?: boolean;
  dataCy?: string;
  defaultCountry?: CountryCode;
  className?: string;
};

export function PhoneNumberInput({
  label,
  value = "",
  onChange,
  onBlur,
  error,
  required = false,
  disabled = false,
  dataCy,
  defaultCountry = DEFAULT_PHONE_COUNTRY,
  className,
}: PhoneNumberInputProps) {
  const inputId = useId();
  const lastEmitted = useRef<string | undefined>(value);

  const [country, setCountry] = useState<CountryCode>(() =>
    splitE164ForEditing(value, defaultCountry).country,
  );
  const [national, setNational] = useState(() =>
    splitE164ForEditing(value, defaultCountry).national,
  );

  const syncFromE164 = useCallback(
    (e164: string | undefined) => {
      const next = splitE164ForEditing(e164, defaultCountry);
      setCountry(next.country);
      setNational(next.national);
    },
    [defaultCountry],
  );

  useEffect(() => {
    if (value === lastEmitted.current) return;
    lastEmitted.current = value;
    syncFromE164(value);
  }, [value, syncFromE164]);

  const emitE164 = useCallback(
    (nationalInput: string, countryCode: CountryCode) => {
      // Preserve the user's input as best-effort E.164 instead of clearing it
      // to "" whenever it is not yet strictly valid — otherwise a typed number
      // can silently vanish from the submitted payload.
      const next = toBestEffortE164(nationalInput, countryCode);
      lastEmitted.current = next;
      onChange(next);
    },
    [onChange],
  );

  const handleNationalChange = (e: ChangeEvent<HTMLInputElement>) => {
    const raw = e.target.value.slice(0, PHONE_UI_MAX_LENGTH);
    const formatted = new AsYouType(country).input(raw);
    setNational(formatted);
    emitE164(formatted, country);
  };

  const handleCountryChange = (e: ChangeEvent<HTMLSelectElement>) => {
    const nextCountry = e.target.value as CountryCode;
    setCountry(nextCountry);
    const formatted = new AsYouType(nextCountry).input(national);
    setNational(formatted);
    emitE164(formatted, nextCountry);
  };

  const handleBlur = () => {
    const e164 = normalizeToE164(national, country);
    if (e164) {
      const display = splitE164ForEditing(e164, country);
      setNational(display.national);
      lastEmitted.current = e164;
      onChange(e164);
    } else {
      // Keep what the user typed (as best-effort E.164) so an invalid number is
      // surfaced by validation rather than silently discarded on blur.
      const next = toBestEffortE164(national, country);
      lastEmitted.current = next;
      onChange(next);
    }
    onBlur?.();
  };

  return (
    <div className={cn("space-y-3", className)}>
      <label
        htmlFor={inputId}
        className="text-body-sm font-semibold capitalize text-dark dark:text-white"
      >
        {label}
        {required && <span className="ml-1 select-none text-error">*</span>}
      </label>

      <div className="mt-3 flex flex-col gap-3 sm:flex-row sm:items-start">
        <Select<FieldValues>
          name={"phoneCountry" as never}
          label="Country code"
          items={PHONE_COUNTRIES}
          searchable
          searchPlaceholder="Search country or code…"
          value={country}
          onChange={handleCountryChange as CountrySelectOnChange}
          disabled={disabled}
          className={cn(
            "w-full shrink-0 space-y-0 sm:w-[min(100%,13.5rem)]",
            "[&_label]:sr-only",
          )}
          dataCy={dataCy ? `${dataCy}-country` : undefined}
        />

        <div className="min-w-0 flex-1">
          <label htmlFor={inputId} className="sr-only">
            {label}
          </label>
          <input
            id={inputId}
            type="tel"
            inputMode="tel"
            autoComplete="tel-national"
            disabled={disabled}
            value={national}
            onChange={handleNationalChange}
            onBlur={handleBlur}
            placeholder="(555) 123-4567"
            maxLength={PHONE_UI_MAX_LENGTH}
            data-cy={dataCy}
            aria-invalid={!!error}
            aria-describedby={error ? `${inputId}-error` : undefined}
            className={cn(
              "w-full rounded-xl border border-stroke/70 bg-white/90 px-5.5 py-3 text-sm shadow-[0_1px_2px_rgba(16,24,40,0.04),0_8px_20px_rgba(16,24,40,0.04)] outline-none backdrop-blur-[1px] transition-all duration-200 placeholder:text-dark-5 focus:border-primary/70 focus:bg-white focus:shadow-[0_0_0_2px_rgba(59,130,246,0.14),0_10px_24px_rgba(59,130,246,0.08)] dark:border-dark-3/80 dark:bg-dark-2/85 dark:text-white dark:placeholder:text-dark-6 dark:focus:border-primary/80 dark:focus:shadow-[0_0_0_2px_rgba(59,130,246,0.2),0_10px_24px_rgba(15,23,42,0.45)]",
              error &&
                "border-red/80 focus:border-red focus:shadow-[0_0_0_2px_rgba(239,68,68,0.16)] dark:border-red/80",
              disabled && "cursor-not-allowed opacity-60",
            )}
          />
        </div>
      </div>

      {error ? (
        <p
          id={`${inputId}-error`}
          className="text-sm text-red-500"
          role="alert"
        >
          {error}
        </p>
      ) : null}
    </div>
  );
}
