"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { ChevronDown, Search } from "lucide-react";
import countryCodes from "@/src/lib/constants/countryCodes.json";
import { detectCountryIso } from "@/src/lib/utils/detectCountry";

interface Country {
  name: string;
  iso2: string;
  dial: string;
  flag: string;
}

const COUNTRIES = countryCodes as Country[];

interface PhoneInputProps {
  /** Combined value stored in the form, e.g. "+91 9876543210". */
  value: string;
  /** Emits the combined "<dial> <national>" string (or "" when empty). */
  onChange: (combined: string) => void;
  onBlur?: () => void;
  placeholder?: string;
  error?: string;
  disabled?: boolean;
  /** ISO2 used until (and if) IP detection resolves. */
  defaultIso?: string;
}

/**
 * We always store the value as "<dial> <national>" (a single space between the
 * two), so splitting on the first space reliably recovers both parts — even
 * across a page reload where the value is restored from the persisted form.
 */
function parseValue(value: string): { dial: string | null; national: string } {
  const trimmed = (value || "").trim();
  if (trimmed.startsWith("+") && trimmed.includes(" ")) {
    const idx = trimmed.indexOf(" ");
    return { dial: trimmed.slice(0, idx), national: trimmed.slice(idx + 1).trim() };
  }
  // No dial recorded yet — treat the whole thing as the national number.
  return { dial: null, national: trimmed.replace(/^\+/, "") };
}

function combine(dial: string, national: string): string {
  const n = national.trim();
  return n ? `${dial} ${n}` : "";
}

/**
 * Display-only grouping for the national part, matching the mask the class
 * registration form used before it moved to this component: "(123) 456-7890".
 *
 * Display only — the value handed to the form stays "<dial> <digits>" with no
 * punctuation, so parseValue(), the stored/prefilled shape and the "10 to 14
 * digits" validation all keep working untouched.
 *
 * Unlike the old formatPhoneNumber, anything past 14 digits is appended rather
 * than silently discarded, so a long number is never truncated as you type.
 */
function formatNational(digits: string): string {
  if (!digits) return "";
  if (digits.length <= 3) return digits;
  if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
  if (digits.length <= 10) {
    return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
  }
  return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6, 10)} ${digits.slice(10)}`;
}

export default function PhoneInput({
  value,
  onChange,
  onBlur,
  placeholder,
  error,
  disabled = false,
  defaultIso = "us",
}: PhoneInputProps) {
  const byIso = useMemo(() => {
    const m = new Map<string, Country>();
    COUNTRIES.forEach((c) => m.set(c.iso2, c));
    return m;
  }, []);

  const firstDialMatch = (dial: string) => COUNTRIES.find((c) => c.dial === dial);

  const initial = useMemo(() => parseValue(value), []); // eslint-disable-line react-hooks/exhaustive-deps
  const [national, setNational] = useState(initial.national);
  const [selectedIso, setSelectedIso] = useState<string>(() => {
    if (initial.dial) return firstDialMatch(initial.dial)?.iso2 || defaultIso;
    return defaultIso;
  });

  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const wrapRef = useRef<HTMLDivElement>(null);
  const userPicked = useRef(false);
  const hadInitialDial = useRef(Boolean(initial.dial));

  // onChange is a fresh closure each render; keep it in a ref so the one-shot
  // IP-detection effect below doesn't re-run (and re-fetch) on every keystroke.
  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;

  const selected = byIso.get(selectedIso) || byIso.get(defaultIso) || COUNTRIES[0];

  // IP-based default. Skips when the value already carried a dial (restored) or
  // once the user has picked a country themselves.
  useEffect(() => {
    if (hadInitialDial.current) return;
    const controller = new AbortController();
    detectCountryIso(controller.signal).then((iso) => {
      if (!iso || userPicked.current || !byIso.has(iso)) return;
      setSelectedIso(iso);
      const c = byIso.get(iso)!;
      // Re-emit so the stored value reflects the detected dial if a number was
      // already typed while detection was in flight.
      setNational((n) => {
        if (n.trim()) onChangeRef.current(combine(c.dial, n));
        return n;
      });
    });
    return () => controller.abort();
  }, [byIso]);

  // Close the dropdown on outside click / Escape.
  useEffect(() => {
    if (!open) return;
    const onDown = (e: MouseEvent) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);
    };
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDown);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const handleNationalChange = (raw: string) => {
    // Digits only: keeps the stored value as "<dial> <digits>" with a single
    // separator space, which the form's phone validation accepts.
    const cleaned = raw.replace(/\D/g, "");
    setNational(cleaned);
    onChange(combine(selected.dial, cleaned));
  };

  const handlePickCountry = (c: Country) => {
    userPicked.current = true;
    setSelectedIso(c.iso2);
    setOpen(false);
    setQuery("");
    onChange(combine(c.dial, national));
  };

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return COUNTRIES;
    return COUNTRIES.filter(
      (c) => c.name.toLowerCase().includes(q) || c.dial.includes(q) || c.iso2.includes(q),
    );
  }, [query]);

  return (
    <div className="w-full phone-input-field">
      <div ref={wrapRef} className="relative w-full">
        <div
          className={`flex items-center w-full border ${
            error ? "border-red-500" : "border-[#58585A]"
          } rounded-full ${disabled ? "bg-gray-100 opacity-60" : ""}`}
        >
          {/* Country selector */}
          <button
            type="button"
            disabled={disabled}
            onClick={() => setOpen((o) => !o)}
            aria-label="Select country code"
            className="flex items-center gap-1.5 pl-[16px] md:pl-[22px] pr-2 py-[12px] lg:py-[10px] md:py-[10px] shrink-0 cursor-pointer disabled:cursor-not-allowed"
          >
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src={selected.flag}
              alt={selected.name}
              width={22}
              height={16}
              className="w-[22px] h-[16px] object-cover rounded-[2px]"
            />
            <span className="text-[#58585A] text-[14px] md:text-[16px]">{selected.dial}</span>
            <ChevronDown size={16} className="text-[#828282]" />
          </button>

          <span className="w-px h-[22px] bg-[#D0D0D0] shrink-0" />

          {/* National number */}
          <input
            type="tel"
            inputMode="tel"
            placeholder={placeholder || "Phone Number"}
            /* Masked for display; handleNationalChange strips it straight back
               to digits, so the stored value is unaffected. */
            value={formatNational(national)}
            onChange={(e) => handleNationalChange(e.target.value)}
            onBlur={onBlur}
            disabled={disabled}
            className="flex-1 min-w-0 bg-transparent py-[12px] lg:py-[10px] md:py-[10px] pl-3 pr-[20px] md:pr-[30px] text-[#828282] text-[14px] md:text-[16px] outline-none rounded-r-full disabled:cursor-not-allowed"
          />
        </div>

        {/* Dropdown */}
        {open && !disabled && (
          <div className="absolute z-50 mt-2 w-full max-w-[340px] bg-white rounded-2xl shadow-[0_18px_50px_-12px_rgba(0,41,74,0.35)] ring-1 ring-black/5 overflow-hidden">
            <div className="flex items-center gap-2 px-3 py-2 border-b border-[#EEF3F7]">
              <Search size={16} className="text-[#9AA5B1] shrink-0" />
              <input
                autoFocus
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Search country"
                className="w-full text-[14px] text-[#4B4B4D] outline-none"
              />
            </div>
            <ul className="max-h-[260px] overflow-y-auto py-1">
              {filtered.length === 0 && (
                <li className="px-4 py-3 text-[13px] text-[#9AA5B1]">No matches</li>
              )}
              {filtered.map((c) => (
                <li key={c.iso2}>
                  <button
                    type="button"
                    onClick={() => handlePickCountry(c)}
                    className={`flex items-center gap-3 w-full px-4 py-2 text-left hover:bg-[#F3F8FC] transition-colors ${
                      c.iso2 === selectedIso ? "bg-[#F3F8FC]" : ""
                    }`}
                  >
                    {/* eslint-disable-next-line @next/next/no-img-element */}
                    <img
                      src={c.flag}
                      alt={c.name}
                      width={24}
                      height={18}
                      className="w-[24px] h-[18px] object-cover rounded-[2px] shrink-0"
                    />
                    <span className="flex-1 text-[14px] text-[#4B4B4D] truncate">{c.name}</span>
                    <span className="text-[13px] text-[#828282]">{c.dial}</span>
                  </button>
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>

      {error && (
        <p className="text-red-500 text-[12px] md:text-[13px] mt-1 ml-4">{error}</p>
      )}
    </div>
  );
}
