"use client";

import classNames from "classnames/dedupe";
import type { ReactNode, RefAttributes } from "react";
import {
  Children,
  isValidElement,
  useLayoutEffect,
  useMemo,
  useRef,
} from "react";
import type { DOMProps } from "../../types/dom";
import type { HTMLElementEvent } from "../../types/events";
import { ErrorMessage } from "../ErrorMessage";
import { Label } from "../Label";
import "./Radio.css";

export type RadioElementType = HTMLInputElement;

export type RadioOverflowInfo = {
  vertical: boolean;
  horizontal: boolean;
};

export type AriaRadioProps = {
  /**
   * Defines a string value that labels the current element.
   */
  "aria-label"?: string;
  /**
   * Identifies the element (or elements) that labels the current element.
   */
  "aria-labelledby"?: string;
  /**
   * Identifies the element (or elements) that describes the object.
   */
  "aria-describedby"?: string;
  /**
   * Identifies the element (or elements) that provide a detailed, extended description for the object.
   */
  "aria-details"?: string;
};

export interface RadioProps
  extends DOMProps, AriaRadioProps, RefAttributes<RadioElementType> {
  children?: ReactNode;
  className?: string;
  label?: ReactNode;
  errorMessage?: string;
  value: string;
  isDisabled?: boolean;
  onChange?: (event: HTMLElementEvent<RadioElementType>) => void;
  defaultChecked?: boolean;
  /**
   * Callback fired when label overflow state changes.
   * Only invoked when the Radio is in horizontal orientation to prevent infinite loops with autoStack.
   * Provides information about vertical and horizontal overflow.
   */
  onOverflow?: (overflow: RadioOverflowInfo) => void;
  // Internal:** Do not use
  groupDisabled?: boolean;
  // Internal:** Do not use
  name?: string;
  // Internal:** Do not use
  selected?: string;
  // Internal:** Do not use
  setSelected?: React.Dispatch<React.SetStateAction<string>>;
  isRequired?: boolean;
  /**
   * **Internal:** Passed via cloneElement from RadioGroup.
   * @internal
   */
  orientation?: "horizontal" | "vertical";
}

const Radio = ({ ref, ...props }: RadioProps) => {
  const {
    children,
    value,
    label,
    className,
    isDisabled,
    errorMessage,
    onChange,
    defaultChecked,
    groupDisabled,
    name,
    selected,
    setSelected,
    isRequired,
    onOverflow,
    orientation,
    ...otherProps
  } = props;
  const realDisabled = groupDisabled || isDisabled;
  const isMultiline = label && children;
  const isControlled = selected !== undefined;
  const isChecked = isControlled ? selected === value : defaultChecked;

  const contentRef = useRef<HTMLDivElement>(null);
  const prevOverflowRef = useRef<RadioOverflowInfo>({
    vertical: false,
    horizontal: false,
  });

  const hasIconFirst = useMemo(() => {
    if (!children || Children.count(children) === 0) return false;

    const firstChild = Children.toArray(children)[0];
    if (!isValidElement(firstChild)) return false;

    const props = firstChild.props as Record<string, unknown>;
    return "icon" in props && props.icon !== undefined;
  }, [children]);

  // Detect overflow and call callback
  useLayoutEffect(() => {
    if (!contentRef.current || !onOverflow) return;

    // Only detect overflow when in horizontal orientation
    // This prevents infinite loops when autoStack switches to vertical
    if (orientation === "vertical") {
      return;
    }

    const element = contentRef.current;

    // Check for content being cut off (true overflow)
    const scrollOverflowVertical = element.scrollHeight > element.clientHeight;
    const scrollOverflowHorizontal = element.scrollWidth > element.clientWidth;

    // Check for multi-line text wrapping by comparing height to single line height
    const styles = window.getComputedStyle(element);
    const lineHeight = parseFloat(styles.lineHeight);
    const fontSize = parseFloat(styles.fontSize);
    const singleLineHeight = isNaN(lineHeight) ? fontSize * 1.2 : lineHeight;

    // Tolerance multiplier to account for sub-pixel rendering and line-height variations.
    // If element height is greater than single line, text has wrapped.
    const WRAP_DETECTION_TOLERANCE = 1.1;
    const hasWrapped =
      element.clientHeight > singleLineHeight * WRAP_DETECTION_TOLERANCE;

    const vertical = scrollOverflowVertical || hasWrapped;
    const horizontal = scrollOverflowHorizontal;

    const newOverflowState = { vertical, horizontal };
    const prevOverflow = prevOverflowRef.current;

    // Only call callback if state actually changed
    if (
      newOverflowState.vertical !== prevOverflow.vertical ||
      newOverflowState.horizontal !== prevOverflow.horizontal
    ) {
      prevOverflowRef.current = newOverflowState;
      onOverflow(newOverflowState);
    }
  }, [label, children, onOverflow, orientation]);

  const radioClasses = {
    "--is-disabled": realDisabled,
    "--is-selected": selected === value,
    "--is-multiline": !!isMultiline,
    "--is-required": isRequired,
  };
  const containerClasses = classNames(
    "mobius-radio__label",
    radioClasses,
    className,
    { "--has-icon-first": hasIconFirst },
  );

  const inputClasses = classNames("mobius-radio__input", radioClasses);

  // Exclude props that are passed via cloneElement in `<RadioGroup />`
  const { "aria-describedby": _ariaDescribedBy, ...rest } = otherProps;

  // Handle all interactions through onChange event
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // Update internal state for RadioGroup
    if (setSelected) {
      setSelected(event.target.value);
    }

    // Call parent onChange callback
    if (onChange) {
      // Create an HTMLElementEvent-compatible object
      const adaptedEvent: HTMLElementEvent<HTMLInputElement> = {
        ...event.nativeEvent,
        target: event.target,
      };
      onChange(adaptedEvent);
    }
  };

  return (
    <>
      <Label className={containerClasses}>
        <input
          aria-describedby={otherProps["aria-describedby"]}
          disabled={realDisabled}
          ref={ref}
          className={inputClasses}
          value={value}
          tabIndex={0}
          type="radio"
          onChange={handleChange}
          {...(isControlled
            ? { checked: isChecked }
            : { defaultChecked: isChecked })}
          name={name}
          required={isRequired}
          {...rest}
        />
        {isMultiline ? (
          <div ref={contentRef} className="mobius-radio__content--multiline">
            <div className="mobius-radio__content-first-line">{label}</div>
            <div className="mobius-radio__extra-content">{children}</div>
          </div>
        ) : (
          <div ref={contentRef} className="mobius-radio__content">
            {label || children}
          </div>
        )}
      </Label>
      {errorMessage && <ErrorMessage errorMessage={errorMessage} />}
    </>
  );
};

Radio.displayName = "Radio";
export { Radio };
