"use client";

import { useEffect, useRef, useState, type ClipboardEvent, type KeyboardEvent } from "react";
import { CheckCircle, Loader2, X } from "lucide-react";
import { ApiError, apiRequest } from "@/lib/api";
import { readCafeToken, readChallenge } from "@/lib/cafeSession";
import { useI18n } from "@/lib/i18n";
import CountryCodeSelect from "@/components/CountryCodeSelect";

const OTP_LENGTH = 6;
const emptyOtp = () => Array.from({ length: OTP_LENGTH }, () => "");
let lastCurrentCodeAt = 0;

function textOf(value: unknown) {
  if (typeof value === "string") return value.trim();
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
  return "";
}

function recordOf(value: unknown) {
  return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}

function readProof(data: unknown) {
  const payload = recordOf(data);
  return textOf(payload.change_proof || payload.proof);
}

export default function PhoneChangeDialog({
  currentPhone,
  onClose,
  onSuccess,
}: {
  currentPhone: string;
  onClose: () => void;
  onSuccess: (phone: string) => void;
}) {
  const { t, lang } = useI18n();
  const [step, setStep] = useState<"current" | "newPhone" | "newCode">("current");
  const [otp, setOtp] = useState(emptyOtp);
  const otpRefs = useRef<(HTMLInputElement | null)[]>([]);
  const otpValueRef = useRef(otp);
  otpValueRef.current = otp;
  const [countryCode, setCountryCode] = useState("");
  const [phone, setPhone] = useState("");
  const [challengeId, setChallengeId] = useState("");
  const [proof, setProof] = useState("");
  const [timer, setTimer] = useState(0);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});

  const applyChallenge = (data: unknown) => {
    const challenge = readChallenge(data);
    if (challenge.id) setChallengeId(challenge.id);
    setTimer(challenge.ttl);
  };

  const sendCurrentCode = async () => {
    setLoading(true);
    setError("");
    setFieldErrors({});
    try {
      const result = await apiRequest("cafe/phone-change/current-code", {
        method: "POST",
        body: new FormData(),
        lang,
        token: readCafeToken(),
      });
      applyChallenge(result.data);
      setStep("current");
      setOtp(emptyOtp());
    } catch (caught) {
      if (caught instanceof ApiError) setError(caught.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    const now = Date.now();
    if (now - lastCurrentCodeAt < 1500) {
      setLoading(false);
      return;
    }
    lastCurrentCodeAt = now;
    sendCurrentCode();
  }, []);

  useEffect(() => {
    if (timer <= 0) return;
    const timeout = window.setTimeout(() => setTimer((current) => current - 1), 1000);
    return () => window.clearTimeout(timeout);
  }, [timer]);

  const code = otp.join("");

  const focusOtp = (index: number) => {
    const el = otpRefs.current[index];
    if (!el) return;
    el.focus();
    el.select();
  };

  const canAccessOtp = (index: number, digits: string[] = otpValueRef.current) =>
    index === 0 || digits.slice(0, index).every((digit) => digit !== "");

  const firstEmptyOtpIndex = (digits: string[] = otpValueRef.current) => {
    const empty = digits.findIndex((digit) => !digit);
    return empty === -1 ? digits.length - 1 : empty;
  };

  const updateOtp = (next: string[], focusIndex?: number) => {
    otpValueRef.current = next;
    setOtp(next);
    if (focusIndex !== undefined) queueMicrotask(() => focusOtp(focusIndex));
  };

  const fillOtpFrom = (index: number, digits: string) => {
    if (!canAccessOtp(index)) {
      focusOtp(firstEmptyOtpIndex());
      return;
    }
    const next = [...otpValueRef.current];
    let cursor = index;
    for (const digit of digits.slice(0, OTP_LENGTH - index)) {
      if (!/^\d$/.test(digit)) continue;
      if (!canAccessOtp(cursor, next)) break;
      next[cursor] = digit;
      cursor += 1;
    }
    const last = OTP_LENGTH - 1;
    const focusAt = cursor > last ? last : next[cursor] === "" ? cursor : Math.min(cursor, last);
    updateOtp(next, Math.min(focusAt, last));
  };

  const handleOtpChange = (index: number, value: string) => {
    const digitsOnly = value.replace(/\D/g, "");
    if (!digitsOnly) {
      if (!canAccessOtp(index)) return;
      const next = [...otpValueRef.current];
      next[index] = "";
      updateOtp(next);
      return;
    }
    fillOtpFrom(index, digitsOnly);
  };

  const handleOtpKeyDown = (index: number, event: KeyboardEvent<HTMLInputElement>) => {
    if (/^\d$/.test(event.key)) {
      event.preventDefault();
      fillOtpFrom(index, event.key);
      return;
    }
    if (event.key === "Backspace") {
      event.preventDefault();
      const current = otpValueRef.current;
      if (current[index]) {
        const next = [...current];
        next[index] = "";
        updateOtp(next, index);
        return;
      }
      if (index > 0) {
        const next = [...current];
        next[index - 1] = "";
        updateOtp(next, index - 1);
      }
      return;
    }
    if (event.key === "ArrowLeft") {
      event.preventDefault();
      if (index > 0) focusOtp(index - 1);
      return;
    }
    if (event.key === "ArrowRight") {
      event.preventDefault();
      if (index < OTP_LENGTH - 1 && canAccessOtp(index + 1)) focusOtp(index + 1);
    }
  };

  const handleOtpFocus = (index: number) => {
    if (!canAccessOtp(index)) focusOtp(firstEmptyOtpIndex());
  };

  const handleOtpPaste = (index: number, event: ClipboardEvent<HTMLInputElement>) => {
    event.preventDefault();
    const pasted = event.clipboardData.getData("text").replace(/\D/g, "");
    if (pasted) fillOtpFrom(index, pasted);
  };

  useEffect(() => {
    if (step === "newPhone" || loading) return;
    const id = requestAnimationFrame(() => focusOtp(0));
    return () => cancelAnimationFrame(id);
  }, [step, loading]);

  const verify = async (next: "newPhone" | "done") => {
    if (code.length !== OTP_LENGTH) {
      setError(t("invalidOtp"));
      return;
    }
    setLoading(true);
    setError("");
    setFieldErrors({});
    try {
      const body = new FormData();
      if (challengeId) body.append("challenge_id", challengeId);
      body.append("code", code);
      const result = await apiRequest("cafe/phone-change/verify", {
        method: "POST",
        body,
        lang,
        token: readCafeToken(),
      });
      const nextProof = readProof(result.data);
      if (nextProof) setProof(nextProof);
      if (next === "done") {
        onSuccess(`+${countryCode} ${phone}`);
        return;
      }
      setOtp(emptyOtp());
      setStep("newPhone");
    } catch (caught) {
      if (caught instanceof ApiError) {
        setFieldErrors(caught.fields);
        if (!Object.keys(caught.fields).length) setError(caught.message);
      }
    } finally {
      setLoading(false);
    }
  };

  const sendNewCode = async () => {
    setLoading(true);
    setError("");
    setFieldErrors({});
    try {
      const body = new FormData();
      if (proof) body.append("change_proof", proof);
      body.append("country_code", countryCode);
      body.append("phone", phone);
      const result = await apiRequest("cafe/phone-change/new-code", {
        method: "POST",
        body,
        lang,
        token: readCafeToken(),
      });
      applyChallenge(result.data);
      setOtp(emptyOtp());
      setStep("newCode");
    } catch (caught) {
      if (caught instanceof ApiError) {
        setFieldErrors(caught.fields);
        if (!Object.keys(caught.fields).length) setError(caught.message);
      }
    } finally {
      setLoading(false);
    }
  };

  const resend = async () => {
    if (timer > 0 || loading) return;
    setLoading(true);
    setError("");
    try {
      const body = new FormData();
      if (challengeId) body.append("challenge_id", challengeId);
      const result = await apiRequest("cafe/phone-change/resend", {
        method: "POST",
        body,
        lang,
        token: readCafeToken(),
      });
      applyChallenge(result.data);
    } catch (caught) {
      if (caught instanceof ApiError) setError(caught.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 p-4">
      <div className="w-full max-w-[448px] rounded-2xl border border-[#E8E4D9] bg-white p-[25px] shadow-[0px_25px_50px_-12px_rgba(0,0,0,0.25)]">
        <div className="mb-6 flex h-8 items-center justify-between">
          <h3 className="text-base font-bold leading-6 text-[#1A1A2E]">{t("changeMobile")}</h3>
          <button
            type="button"
            onClick={onClose}
            className="flex size-8 items-center justify-center rounded-lg text-[#4A4A6A] hover:bg-[#FAFAF8]"
          >
            <X className="size-4" />
          </button>
        </div>

        {step === "newPhone" ? (
          <div className="flex flex-col gap-4">
            <div>
              <label className="mb-2 block text-sm font-bold leading-5 text-[#1A1A2E]">{t("newMobile")}</label>
              <div dir="ltr" className="relative flex h-12 items-stretch rounded-xl border-2 border-[#E8E4D9] bg-[#FAFAF8] p-[2px] focus-within:border-[#C9A84C]">
                <input
                  type="tel"
                  value={phone}
                  onChange={(event) => setPhone(event.target.value.replace(/\D/g, ""))}
                  placeholder="5XXXXXXXX"
                  maxLength={15}
                  className="h-full min-w-0 flex-1 bg-transparent px-3 text-sm text-[#1A1A2E] outline-none"
                />
                <CountryCodeSelect value={countryCode} onChange={setCountryCode} />
              </div>
              {fieldErrors.phone ? <p className="mt-1 text-xs text-red-500">{fieldErrors.phone}</p> : null}
              {fieldErrors.country_code ? <p className="mt-1 text-xs text-red-500">{fieldErrors.country_code}</p> : null}
              {fieldErrors.change_proof ? <p className="mt-1 text-xs text-red-500">{fieldErrors.change_proof}</p> : null}
            </div>
          </div>
        ) : (
          <div>
            <p className="mb-4 flex flex-wrap items-center justify-center gap-1 text-center text-sm leading-5">
              <span dir="ltr" className="font-bold text-[#1A1A2E]">
                {step === "current" ? currentPhone : `+${countryCode} ${phone}`}
              </span>
              <span className="text-[#4A4A6A]">{t("enterOtp")}</span>
            </p>
            <div className="grid grid-cols-6 gap-1.5 sm:gap-2" dir="ltr">
              {otp.map((digit, index) => (
                <input
                  key={index}
                  ref={(element) => { otpRefs.current[index] = element; }}
                  type="text"
                  inputMode="numeric"
                  autoComplete={index === 0 ? "one-time-code" : "off"}
                  value={digit}
                  maxLength={1}
                  tabIndex={canAccessOtp(index) ? 0 : -1}
                  onChange={(event) => handleOtpChange(index, event.target.value)}
                  onKeyDown={(event) => handleOtpKeyDown(index, event)}
                  onFocus={() => handleOtpFocus(index)}
                  onClick={() => handleOtpFocus(index)}
                  onPaste={(event) => handleOtpPaste(index, event)}
                  className="h-14 w-full min-w-0 rounded-xl border-2 border-[#E8E4D9] bg-white p-[2px] text-center text-xl font-bold leading-7 text-[#1A1A2E] outline-none transition-all focus:border-[#C9A84C]"
                />
              ))}
            </div>
            {fieldErrors.code ? <p className="mt-1 text-xs text-red-500">{fieldErrors.code}</p> : null}
            <div className="mt-3 text-center">
              {timer > 0 ? (
                <p className="text-xs leading-4 text-[#4A4A6A]">{t("resendIn")} {timer}s</p>
              ) : (
                <button type="button" onClick={resend} disabled={loading} className="text-xs font-medium text-[#C9A84C] disabled:opacity-50">
                  {t("resendCode")}
                </button>
              )}
            </div>
          </div>
        )}

        {error ? <p className="mt-3 text-xs text-red-500">{error}</p> : null}

        <button
          type="button"
          disabled={loading}
          onClick={() => {
            if (step === "newPhone") sendNewCode();
            else if (step === "current") verify("newPhone");
            else verify("done");
          }}
          className="mt-6 flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-br from-[#C9A84C] via-[#E8C94A] to-[#A07830] text-sm font-bold text-[#1A1A2E] transition-opacity hover:opacity-90 disabled:opacity-50"
        >
          {loading ? <Loader2 className="size-4 animate-spin" /> : <CheckCircle className="size-4" />}
          {step === "newPhone" ? t("sendOtp") : t("verifyOtp")}
        </button>
      </div>
    </div>
  );
}
