"use client";

import { useState, type Ref } from "react";
import { cx } from "../../utils";

export interface FoxToggleQuantityProps {
  /** 주면 제어 컴포넌트가 된다 — 호출부가 소유하고, 이 요소가 스스로 바꾸지 않는다. */
  value?: number;
  /** 비제어로 쓸 때의 초기 값. `value`를 주면 무시된다. */
  defaultValue?: number;
  /**
   * 값이 바뀔 때 호출된다. 이미 `min`·`max`로 자른 뒤의 값이 넘어온다.
   *
   * ⚠️ 이름이 `onChange`가 아니다 — 네이티브 입력을 감싸는 게 아니라 버튼 둘로 만든 커스텀
   * 위젯이라 `FoxSelect.onValueChange`와 같은 쪽이다.
   */
  onValueChange?: (value: number) => void;
  /** 내려갈 수 있는 하한. 값이 여기 닿으면 빼기 버튼이 비활성된다. */
  min?: number;
  /** 올라갈 수 있는 상한. 넘기지 않으면 상한이 없어 더하기 버튼이 비활성되지 않는다. */
  max?: number;
  /** 한 번에 오르내리는 폭. */
  step?: number;
  /**
   * 폼으로 값을 보낼 이름. 주면 같은 값을 담은 `<input type="hidden">`을 함께 렌더한다.
   *
   * `FoxChip`은 이걸 열지 않는다 — 칩은 여러 개가 한 묶음으로 하나의 값을 이뤄서 이름 하나를
   * 칩 하나에 묶을 수 없기 때문이다. 이쪽은 값이 정확히 하나라 그 문제가 없다.
   */
  name?: string;
  /** 두 버튼을 모두 비활성한다. `min`·`max`에 의한 개별 비활성과는 별개다. */
  disabled?: boolean;
  /** 묶음을 읽어 줄 이름. 화면에 보이는 제목이 이미 있으면 `labelledBy`를 쓴다. */
  label?: string;
  /** 묶음 이름 역할을 하는 요소의 id. `label`보다 우선한다. */
  labelledBy?: string;
  /** 빼기 버튼을 읽어 줄 이름. */
  decreaseLabel?: string;
  /** 더하기 버튼을 읽어 줄 이름. */
  increaseLabel?: string;
  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
  hidden?: boolean;
  /** 배치 조정용. 시안의 폭(140px)을 바꿔야 할 때도 여기로 준다. */
  className?: string;
  ref?: Ref<HTMLDivElement>;
}

/**
 * @fox 수량 조절기. 빼기 버튼 · 숫자 · 더하기 버튼이 회색 판 위에 놓인다.
 *
 * 값은 제어·비제어 둘 다 된다(`FoxSelect`와 같은 방식). `min`·`max`에 닿으면 해당 버튼이
 * 네이티브 `disabled`가 되므로, 누를 수 없다는 것이 커서와 보조기술에 함께 전달된다.
 *
 * 숫자는 입력칸이 아니라 읽기 전용 글자다 — 시안에 입력칸의 테두리도 배경도 없다. 그래서
 * `role="spinbutton"`을 붙이지 않고 `role="group"` 안의 버튼 둘로 두고, 바뀐 값은
 * `aria-live`로 알린다. 키보드로 값을 직접 치는 자리가 필요해지면 시안에 먼저 추가한다.
 *
 * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
 * (또는 개별 파티셜)로 한 번 불러와야 한다.
 */
export function FoxToggleQuantity({
  value,
  defaultValue = 0,
  onValueChange,
  min = 0,
  max,
  step = 1,
  name,
  disabled = false,
  label,
  labelledBy,
  decreaseLabel = "수량 줄이기",
  increaseLabel = "수량 늘리기",
  hidden = false,
  className,
  ref,
}: FoxToggleQuantityProps) {
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);

  const currentValue = value ?? uncontrolledValue;

  // 훅을 전부 부른 뒤에 빠져나간다 — 위쪽에서 반환하면 훅 호출 순서가 깨진다.
  if (hidden) {
    return null;
  }

  // 호출부가 범위 밖의 값을 줄 수도 있으므로 표시 직전에 한 번 자른다 — 자르지 않으면
  // 버튼 비활성 여부와 보이는 숫자가 어긋난다.
  const clamp = (next: number) => Math.min(max ?? Infinity, Math.max(min, next));

  const atMin = currentValue <= min;
  const atMax = max !== undefined && currentValue >= max;

  function commit(next: number) {
    const clamped = clamp(next);
    if (clamped === currentValue) {
      return;
    }
    // 제어 컴포넌트일 때는 내부 값을 건드리지 않는다 — 건드리면 호출부가 값을 거부해도
    // 내부에만 반영되어 둘이 조용히 갈라진다.
    if (value === undefined) setUncontrolledValue(clamped);
    onValueChange?.(clamped);
  }

  return (
    <div
      ref={ref}
      role="group"
      aria-label={labelledBy ? undefined : label}
      aria-labelledby={labelledBy}
      className={cx("fox-toggle-quantity", className)}
    >
      <button
        type="button"
        className="fox-toggle-quantity__button"
        disabled={disabled || atMin}
        aria-label={decreaseLabel}
        onClick={() => commit(currentValue - step)}
      >
        <span className="fox-toggle-quantity__icon" aria-hidden="true">
          <MinusMark />
        </span>
      </button>

      {/* 값이 바뀐 것을 소리로도 알린다 — 버튼 이름은 "수량 줄이기"라 결과를 담지 않는다. */}
      <span className="fox-toggle-quantity__value" aria-live="polite">
        {clamp(currentValue)}
      </span>

      <button
        type="button"
        className="fox-toggle-quantity__button"
        disabled={disabled || atMax}
        aria-label={increaseLabel}
        onClick={() => commit(currentValue + step)}
      >
        <span className="fox-toggle-quantity__icon" aria-hidden="true">
          <PlusMark />
        </span>
      </button>

      {name ? <input type="hidden" name={name} value={clamp(currentValue)} /> : null}
    </div>
  );
}

// 시안 아이콘 둘. `fill`을 시안의 리터럴(#58616A) 대신 `currentColor`로 두면 색을 버튼
// 규칙이 정한다(그쪽에서 같은 값을 `primitive(neutral-60)`으로 가리킨다).
// `width`·`height`를 쓰지 않는다 — 크기는 슬롯이 정하고 SVG는 100%로 따라온다.

function MinusMark() {
  return (
    <svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
      <path
        d="M17.5 10C17.5 10.1658 17.4342 10.3247 17.3169 10.4419C17.1997 10.5592 17.0408 10.625 16.875 10.625H3.125C2.95924 10.625 2.80027 10.5592 2.68306 10.4419C2.56585 10.3247 2.5 10.1658 2.5 10C2.5 9.83424 2.56585 9.67527 2.68306 9.55806C2.80027 9.44085 2.95924 9.375 3.125 9.375H16.875C17.0408 9.375 17.1997 9.44085 17.3169 9.55806C17.4342 9.67527 17.5 9.83424 17.5 10Z"
        fill="currentColor"
      />
    </svg>
  );
}

function PlusMark() {
  return (
    <svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
      <path
        d="M17.5 10C17.5 10.1658 17.4342 10.3247 17.3169 10.4419C17.1997 10.5592 17.0408 10.625 16.875 10.625H10.625V16.875C10.625 17.0408 10.5592 17.1997 10.4419 17.3169C10.3247 17.4342 10.1658 17.5 10 17.5C9.83424 17.5 9.67527 17.4342 9.55806 17.3169C9.44085 17.1997 9.375 17.0408 9.375 16.875V10.625H3.125C2.95924 10.625 2.80027 10.5592 2.68306 10.4419C2.56585 10.3247 2.5 10.1658 2.5 10C2.5 9.83424 2.56585 9.67527 2.68306 9.55806C2.80027 9.44085 2.95924 9.375 3.125 9.375H9.375V3.125C9.375 2.95924 9.44085 2.80027 9.55806 2.68306C9.67527 2.56585 9.83424 2.5 10 2.5C10.1658 2.5 10.3247 2.56585 10.4419 2.68306C10.5592 2.80027 10.625 2.95924 10.625 3.125V9.375H16.875C17.0408 9.375 17.1997 9.44085 17.3169 9.55806C17.4342 9.67527 17.5 9.83424 17.5 10Z"
        fill="currentColor"
      />
    </svg>
  );
}
