"use client";

import {
  useEffect,
  useRef,
  type ChangeEvent,
  type ComponentPropsWithRef,
  type ReactNode,
} from "react";
import { cx } from "../../utils";
import { FoxCheckIcon } from "../fox-check-icon";

export type FoxCheckboxSize = "lg" | "md";

/**
 * 네이티브 `<input type="checkbox">`가 받는 것을 그대로 넘겨받는다 — name·value·checked·
 * defaultChecked·disabled·required·form 등. 의미가 겹치는 것만 걷어내고 아래에서 다시 정의한다.
 */
type NativeCheckboxProps = Omit<
  ComponentPropsWithRef<"input">,
  "type" | "size" | "className" | "children" | "hidden" | "onChange"
>;

export interface FoxCheckboxProps extends NativeCheckboxProps {
  size?: FoxCheckboxSize;
  label: ReactNode;
  /** 라벨 아래 보조 설명. 넘기지 않으면 영역을 렌더링하지 않는다. */
  description?: ReactNode;
  /**
   * 부분선택. "전체 동의"처럼 자식 일부만 켜진 상태를 나타낸다.
   *
   * HTML 속성이 아니라 DOM 프로퍼티라 마크업만으로는 표현할 수 없다 — 이 컴포넌트가 대신
   * 넣어 준다. 켜짐 여부와는 별개라, DOM에서 `checked`와 함께 참일 수 있고 그때는 부분선택
   * 모양이 이긴다.
   */
  indeterminate?: boolean;
  /**
   * 켜지거나 꺼질 때 부른다. 켜짐 여부가 첫 인자다 — 네이티브 `onChange`를 `Omit`으로
   * 걷어내고 그 자리를 대신한다(`FoxInput`과 같은 방식).
   */
  onChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
  /**
   * 글자를 눈에서만 감춘다 — 표의 선택 열처럼 네모(동그라미)만 필요한 자리에 쓴다.
   * DOM에는 남으므로 스크린리더는 그대로 읽는다. `description`도 함께 감춰진다.
   */
  hideLabel?: boolean;
  /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
  hidden?: boolean;
  /** 배치 조정용. */
  className?: string;
}

const SIZE_CLASS: Record<FoxCheckboxSize, string> = {
  lg: "fox-checkbox--lg",
  md: "fox-checkbox--md",
};

/**
 * @fox 체크박스 한 칸. 켜짐 여부는 네이티브 `<input type="checkbox">`가 소유한다.
 *
 * 라디오와 달리 `name`이 같아도 서로를 끄지 않는다. 여러 개를 동시에 고를 수 있고, 폼 전송
 * 시 고른 것들이 함께 나간다.
 *
 * 루트가 `<label>`이라 라벨이나 설명을 눌러도 켜진다. 네모 표시는 CSS가 input의
 * `:checked`·`:indeterminate`·`:disabled`를 보고 그리므로, 비제어로 써도 모양이 어긋나지 않는다.
 *
 * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
 * (또는 개별 파티셜)로 한 번 불러와야 한다.
 */
export function FoxCheckbox({
  size = "lg",
  label,
  description,
  indeterminate = false,
  onChange,
  hideLabel = false,
  hidden = false,
  className,
  ref,
  ...rest
}: FoxCheckboxProps) {
  const inputRef = useRef<HTMLInputElement>(null);

  // `indeterminate`는 마크업으로 못 준다 — 렌더 후 DOM에 직접 넣어야 한다.
  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.indeterminate = indeterminate;
    }
  }, [indeterminate]);

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

  // 호출부 ref와 내부 ref를 함께 채운다 — 부분선택을 넣으려면 안에서도 요소를 쥐어야 한다.
  const attachInput = (node: HTMLInputElement | null) => {
    inputRef.current = node;
    if (typeof ref === "function") {
      ref(node);
    } else if (ref) {
      (ref as { current: HTMLInputElement | null }).current = node;
    }
  };

  // 라디오와 달리 끌 때도 change가 뛴다. 그래서 켜짐 여부를 그대로 넘긴다.
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    onChange?.(event.target.checked, event);
  };

  return (
    <label className={cx("fox-checkbox", SIZE_CLASS[size], hideLabel && "fox-checkbox--hide-label", className)}>
      {/* 형제 선택자(`~`)가 뒤따르는 표시와 본문에 닿아야 하므로 맨 앞에 둔다. */}
      <input
        {...rest}
        ref={attachInput}
        type="checkbox"
        className="fox-checkbox__input"
        onChange={handleChange}
      />
      <FoxCheckIcon size={size} />
      <span className="fox-checkbox__body">
        <span className="fox-checkbox__label">{label}</span>
        {description ? (
          <span className="fox-checkbox__description">{description}</span>
        ) : null}
      </span>
    </label>
  );
}
