"use client";

import {
  Fragment,
  useId,
  useRef,
  useState,
  type ChangeEvent,
  type KeyboardEvent,
  type ReactNode,
} from "react";
import { cx } from "../../utils";
import { FoxInput, type FoxInputState } from "../fox-input";
import { FoxSelect, type FoxSelectItem } from "../fox-select";

export type FoxPhoneNumberType = "unit" | "combine";
export type FoxPhoneNumberState = FoxInputState;

type Parts = [string, string, string];
type Lengths = [number, number, number];

export interface FoxPhoneNumberProps {
  /** `unit`은 상자 셋을 하이픈으로 잇고, `combine`은 상자 하나에 세 칸을 둔다. */
  type?: FoxPhoneNumberType;
  /**
   * 숫자만 담긴 값. 하이픈은 화면에만 있고 값에는 들어가지 않는다 — `"01012345678"`.
   * 꾸며진 값을 넣어도 숫자만 남겨 읽는다. 주면 제어 컴포넌트가 된다.
   */
  value?: string;
  defaultValue?: string;
  onChange?: (value: string) => void;
  /**
   * 앞자리 목록. `unit`에서는 선택 목록이 되고, 두 모양 모두 **밖에서 받은 값을 나눌 때**
   * 앞자리 길이를 알아내는 데 쓴다 — 없으면 `maxLengths[0]`만큼을 앞자리로 본다.
   * 지역번호 `02`처럼 짧은 앞자리를 쓰면 목록에 넣거나 `maxLengths`를 함께 조정한다.
   */
  prefixOptions?: string[];
  label?: string;
  /** 헬퍼 메시지. 색은 상태를 따라간다. */
  message?: string;
  /** 헬퍼 메시지 앞 아이콘. `currentColor`로 그려야 상태 색을 따라간다. */
  messageIcon?: ReactNode;
  placeholders?: Parts;
  /** 칸별 최대 자릿수. 자동 이동의 기준이기도 하다. */
  maxLengths?: Lengths;
  /** 칸을 다 채우면 다음 칸으로 옮기고, 빈 칸에서 지우면 앞칸으로 돌아간다. */
  autoAdvance?: boolean;
  /** 오류 표시. `aria-invalid`가 되고 테두리·헬퍼 색이 바뀐다. */
  invalid?: boolean;
  disabled?: boolean;
  readOnly?: boolean;
  /** 주면 모양이 그 상태로 고정된다. 없으면 포커스·값·비활성으로 브라우저가 판단한다. */
  state?: FoxPhoneNumberState;
  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
  hidden?: boolean;
  /** 배치 조정용. */
  className?: string;
  name?: string;
  id?: string;
}

const DEFAULT_PLACEHOLDERS: Parts = ["010", "1234", "5678"];
const DEFAULT_MAX_LENGTHS: Lengths = [3, 4, 4];
const SEGMENT_LABELS: Parts = ["전화번호 앞자리", "전화번호 가운데자리", "전화번호 뒷자리"];

/** 숫자가 아닌 글자는 버린다 — 붙여넣기로 들어온 하이픈·공백도 여기서 걸린다. */
function onlyDigits(text: string): string {
  return text.replace(/\D/g, "");
}

/**
 * 숫자만 담긴 값을 화면의 세 칸으로 나눈다.
 *
 * 앞자리는 선택 목록 중 가장 긴 일치를 쓴다 — `02`와 `021`이 함께 있어도 흔들리지 않는다.
 * 뒷자리는 항상 마지막 네 자리다(시안 기준). 그래서 `010-123-4567`과 `010-1234-5678`이
 * 둘 다 맞게 갈린다. 남은 글자가 그보다 짧으면 아직 입력 중이라 보고 가운데에 둔다.
 */
function splitValue(value: string, prefixOptions: string[], maxLengths: Lengths): Parts {
  // 밖에서 `010-1234-5678`처럼 꾸며진 값이 와도 받아 준다 — 내보낼 때는 숫자만 남는다.
  const digits = onlyDigits(value ?? "");
  const matched = prefixOptions
    .filter((option) => option && digits.startsWith(option))
    .sort((a, b) => b.length - a.length)[0];
  const head = matched ?? digits.slice(0, maxLengths[0]);
  const rest = digits.slice(head.length);

  if (rest.length > maxLengths[2]) {
    return [head, rest.slice(0, rest.length - maxLengths[2]), rest.slice(-maxLengths[2])];
  }
  return [head, rest, ""];
}

/**
 * @fox 전화번호 입력. 화면은 세 칸이지만 값은 숫자만 담긴 문자열 하나다.
 *
 * 화면의 세 칸이 값의 진실 공급원이다 — 입력 도중 `"0101"`처럼 어디까지가 앞자리인지
 * 알 수 없는 값이 생겨도 칸이 흔들리지 않는다. 밖에서 `value`를 바꿨을 때만 다시 나눈다.
 *
 * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
 * (또는 개별 파티셜)로 한 번 불러와야 한다.
 */
export function FoxPhoneNumber({
  type = "unit",
  value,
  defaultValue,
  onChange,
  prefixOptions = [],
  label,
  message,
  messageIcon,
  placeholders = DEFAULT_PLACEHOLDERS,
  maxLengths = DEFAULT_MAX_LENGTHS,
  autoAdvance = true,
  invalid = false,
  disabled = false,
  readOnly = false,
  state,
  hidden = false,
  className,
  name,
  id,
}: FoxPhoneNumberProps) {
  const autoId = useId();
  const [parts, setParts] = useState<Parts>(() =>
    splitValue(value ?? defaultValue ?? "", prefixOptions, maxLengths)
  );
  const [seenValue, setSeenValue] = useState(value);
  const tailRefs = useRef<(HTMLInputElement | null)[]>([]);

  // 밖에서 값을 바꿨을 때만 다시 나눈다. 우리가 방금 올려보낸 값이면 그대로 둔다.
  if (value !== undefined && value !== seenValue) {
    setSeenValue(value);
    if (value !== parts.join("")) {
      setParts(splitValue(value, prefixOptions, maxLengths));
    }
  }

  if (hidden) {
    return null;
  }

  const rootId = id ?? autoId;
  const labelId = `${rootId}-label`;
  const messageId = `${rootId}-message`;
  const errored = invalid || state === "error";

  const commit = (index: number, next: string) => {
    const nextParts = [...parts] as Parts;
    nextParts[index] = next;
    setParts(nextParts);
    const joined = nextParts.join("");
    setSeenValue(joined);
    onChange?.(joined);
  };

  const focusSegment = (index: number) => {
    tailRefs.current[index]?.focus();
  };

  // 숫자가 아닌 입력은 아예 들어오지 못한다. 붙여넣기로 하이픈이 섞여 와도 여기서 걸리고,
  // 그 뒤에 자릿수를 자르므로 `010-1234`를 한 칸에 붙여도 넘치지 않는다.
  const handleSegmentChange = (index: number) => (next: string) => {
    const digits = onlyDigits(next).slice(0, maxLengths[index]);
    commit(index, digits);
    if (autoAdvance && index < 2 && digits.length >= maxLengths[index]) {
      focusSegment(index + 1);
    }
  };

  // 빈 칸에서 백스페이스하면 앞칸으로 돌아간다.
  const handleSegmentKeyDown = (index: number) => (event: KeyboardEvent<HTMLInputElement>) => {
    if (autoAdvance && event.key === "Backspace" && index > 0 && parts[index] === "") {
      event.preventDefault();
      focusSegment(index - 1);
    }
  };

  const prefixItems: FoxSelectItem[] = prefixOptions.map((option) => ({
    value: option,
    label: option,
  }));

  const body =
    type === "unit" ? (
      <div className="fox-phone-number__row">
        <div className="fox-phone-number__part">
          <FoxSelect
            size="md"
            options={prefixItems}
            value={parts[0]}
            onValueChange={(next) => {
              commit(0, next);
              if (autoAdvance) {
                focusSegment(1);
              }
            }}
            placeholder={placeholders[0]}
            error={errored}
            disabled={disabled || state === "disabled"}
            readOnly={readOnly || state === "view"}
          />
        </div>

        {[1, 2].map((index) => (
          <Fragment key={index}>
            <span className="fox-phone-number__separator" aria-hidden="true">
              -
            </span>
            <div className="fox-phone-number__part">
              <FoxInput
                size="md"
                inputMode="numeric"
                aria-label={SEGMENT_LABELS[index]}
                value={parts[index]}
                onChange={handleSegmentChange(index)}
                onKeyDown={handleSegmentKeyDown(index)}
                placeholder={placeholders[index]}
                maxLength={maxLengths[index]}
                invalid={invalid}
                disabled={disabled}
                readOnly={readOnly}
                state={state}
                ref={(node) => {
                  tailRefs.current[index] = node;
                }}
              />
            </div>
          </Fragment>
        ))}
      </div>
    ) : (
      <div className="fox-phone-number__box">
        {[0, 1, 2].map((index) => (
          <Fragment key={index}>
            {index > 0 && (
              <span className="fox-phone-number__separator" aria-hidden="true">
                -
              </span>
            )}
            <input
              className="fox-phone-number__segment"
              inputMode="numeric"
              aria-label={SEGMENT_LABELS[index]}
              aria-invalid={errored || undefined}
              aria-describedby={message ? messageId : undefined}
              value={parts[index]}
              placeholder={placeholders[index]}
              maxLength={maxLengths[index]}
              disabled={disabled}
              readOnly={readOnly}
              onChange={(event: ChangeEvent<HTMLInputElement>) =>
                handleSegmentChange(index)(event.target.value)
              }
              onKeyDown={handleSegmentKeyDown(index)}
              ref={(node) => {
                tailRefs.current[index] = node;
              }}
            />
          </Fragment>
        ))}
      </div>
    );

  return (
    <div
      className={cx("fox-phone-number", className)}
      data-state={state}
      role="group"
      aria-labelledby={label ? labelId : undefined}
    >
      {label && (
        <span className="fox-phone-number__label" id={labelId}>
          {label}
        </span>
      )}

      {body}

      {/* 폼 전송용. 화면의 세 칸은 이름을 갖지 않는다 — 값은 언제나 합쳐진 하나다. */}
      {name && <input type="hidden" name={name} value={parts.join("")} />}

      {message && (
        <p className="fox-phone-number__message" id={messageId}>
          {messageIcon && (
            <span className="fox-phone-number__message-icon" aria-hidden="true">
              {messageIcon}
            </span>
          )}
          {message}
        </p>
      )}
    </div>
  );
}
