"use client";

import { useId, useState, type ReactNode } from "react";
import { FoxFormLabel } from "../fox-form-label";
import { cx } from "../../utils";
import { FoxButton } from "../fox-button";
import { FoxInput } from "../fox-input";

/**
 * 주소 검색 결과. 호출부가 어떤 우편번호 서비스를 쓰든 이 모양으로 바꿔서 돌려준다 —
 * 그래야 `@fox`가 특정 API에 묶이지 않는다.
 */
export interface FoxAddressSearchResult {
  /** 우편번호. 첫 칸에 그대로 들어간다. */
  zipCode: string;
  /** 도로명 또는 지번 주소. 둘째 칸에 그대로 들어간다. 어느 쪽을 쓸지는 호출부가 정한다. */
  address: string;
  /**
   * 상세주소 칸에 미리 채울 값(건물명 같은 참고항목).
   * 넘기지 않으면 사용자가 적어 둔 상세주소를 건드리지 않는다.
   */
  detail?: string;
}

/** 주소 한 벌. 화면의 세 칸을 그대로 담는다. */
export interface FoxAddressValue {
  zipCode: string;
  address: string;
  detail: string;
}

export const EMPTY_ADDRESS: FoxAddressValue = { zipCode: "", address: "", detail: "" };

export interface FoxAddressProps {
  value?: FoxAddressValue;
  defaultValue?: FoxAddressValue;
  onChange?: (value: FoxAddressValue) => void;

  /**
   * 주소 검색. 호출부가 우편번호 서비스를 띄우고 결과를 `FoxAddressSearchResult`로
   * 돌려준다. 사용자가 고르지 않고 닫으면 `null`을 돌려주면 되고, 그때는 값이 그대로
   * 남는다. throw·reject하면 `onSearchFail`을 부른다.
   */
  onSearch: () => Promise<FoxAddressSearchResult | null>;
  onSearchSuccess?: (result: FoxAddressSearchResult) => void;
  onSearchFail?: (error: unknown) => void;
  searchLabel?: string;

  label?: string;
  /** 헬퍼 메시지. 오류일 때 색이 바뀐다. */
  message?: string;
  messageIcon?: ReactNode;
  zipCodePlaceholder?: string;
  addressPlaceholder?: string;
  detailPlaceholder?: string;

  invalid?: boolean;
  disabled?: boolean;
  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
  hidden?: boolean;
  /** 배치 조정용. */
  className?: string;
}

/**
 * @fox 주소 입력. 우편번호와 주소는 검색 결과로만 채워지고, 상세주소만 손으로 적는다.
 *
 * 어떤 우편번호 서비스를 쓸지는 `@fox`가 알 필요가 없다 — 호출부가 `onSearch`에서 그
 * 서비스를 띄우고 결과를 `FoxAddressSearchResult` 모양으로 바꿔서 돌려주면 된다.
 *
 * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
 * (또는 개별 파티셜)로 한 번 불러와야 한다.
 */
export function FoxAddress({
  value,
  defaultValue,
  onChange,
  onSearch,
  onSearchSuccess,
  onSearchFail,
  searchLabel = "주소 검색",
  label,
  message,
  messageIcon,
  zipCodePlaceholder = "우편번호",
  addressPlaceholder = "주소",
  detailPlaceholder = "상세주소",
  invalid = false,
  disabled = false,
  hidden = false,
  className,
}: FoxAddressProps) {
  const autoId = useId();
  const [inner, setInner] = useState<FoxAddressValue>(defaultValue ?? EMPTY_ADDRESS);
  const [searching, setSearching] = useState(false);

  if (hidden) {
    return null;
  }

  const current = value ?? inner;
  const messageId = `${autoId}-message`;

  const commit = (next: FoxAddressValue) => {
    if (value === undefined) {
      setInner(next);
    }
    onChange?.(next);
  };

  const handleSearch = async () => {
    if (searching) {
      return;
    }
    setSearching(true);
    try {
      const result = await onSearch();
      // 취소하면 `null`이 온다 — 그때는 값이 그대로 남는다.
      if (result) {
        commit({
          zipCode: result.zipCode,
          address: result.address,
          detail: result.detail ?? current.detail,
        });
        onSearchSuccess?.(result);
      }
    } catch (error) {
      onSearchFail?.(error);
    } finally {
      setSearching(false);
    }
  };

  return (
    <div
      className={cx("fox-address", className)}
      role="group"
      aria-labelledby={label ? `${autoId}-label` : undefined}
    >
      {label && (
        <FoxFormLabel as="span" id={`${autoId}-label`}>
          {label}
        </FoxFormLabel>
      )}

      <div className="fox-address__row">
        <div className="fox-address__zip">
          {/* 검색 결과로만 채워진다 — 손으로 고칠 수 없다. */}
          <FoxInput
            size="md"
            aria-label={zipCodePlaceholder}
            aria-describedby={message ? messageId : undefined}
            value={current.zipCode}
            placeholder={zipCodePlaceholder}
            readOnly
            invalid={invalid}
            disabled={disabled}
          />
        </div>

        <FoxButton
          size="md"
          type="secondary"
          label={searchLabel}
          loading={searching}
          disabled={disabled}
          onAction={handleSearch}
        />
      </div>

      <FoxInput
        size="md"
        aria-label={addressPlaceholder}
        value={current.address}
        placeholder={addressPlaceholder}
        readOnly
        invalid={invalid}
        disabled={disabled}
      />

      <FoxInput
        size="md"
        aria-label={detailPlaceholder}
        value={current.detail}
        onChange={(next) => commit({ ...current, detail: next })}
        placeholder={detailPlaceholder}
        invalid={invalid}
        disabled={disabled}
      />

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