"use client";

import classNames from "classnames/dedupe";
import type { ReactElement, ReactNode, RefAttributes } from "react";
import {
  Children,
  cloneElement,
  isValidElement,
  useCallback,
  useEffect,
  useId,
  useRef,
  useState,
} from "react";
import { useValidationClasses } from "../../hooks";
import type { Validation } from "../../types";
import type { DOMProps } from "../../types/dom";
import type { HTMLElementEvent } from "../../types/events";
import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
import { ErrorMessage } from "../ErrorMessage";
import { Label } from "../Label";
import { Stack } from "../Stack";
import type { RadioOverflowInfo } from "./Radio";

export type RadioGroupElementType = HTMLDivElement;

export interface RadioGroupProps
  extends DOMProps, Validation, RefAttributes<RadioGroupElementType> {
  children: ReactNode;
  className?: string;
  orientation?: "horizontal" | "vertical";
  errorMessage?: string;
  // Callback that fires when select changes
  onChange?: (event: HTMLElementEvent<HTMLInputElement>) => void;
  /**
   * Automatically change orientation from horizontal to vertical when any Radio label overflows.
   * Only applies when orientation is set to "horizontal".
   */
  autoStack?: boolean;
  // 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 that provides an error message for the object.
  "aria-errormessage"?: string;
  // Identifies the element (or elements) that describes the object.
  "aria-describedby"?: string;
  // Whether user input is required on the input before form submission.
  isRequired?: boolean;
  // Whether the input is disabled.
  isDisabled?: boolean;
  // Whether the input can be selected but not changed by the user.
  isReadOnly?: boolean;
  // The default value (uncontrolled).
  defaultValue?: string;
  // The content to display as the label.
  label?: ReactNode;
  /**
   * The value of the radio button, used when submitting an HTML form.
   * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value).
   */
  value?: string;
  name?: string;
}

const getDefaultVal = (children: ReactNode, defaultValue?: string) => {
  if (Array.isArray(children) && defaultValue) {
    const option = children?.find(item => item.props.value === defaultValue);

    if (!option) return "";

    return option.props.value;
  }

  return "";
};

const RadioGroup = ({ ref, ...props }: RadioGroupProps) => {
  const {
    label,
    isDisabled = false,
    isRequired,
    isInvalid,
    orientation = "vertical",
    className,
    errorMessage,
    children,
    defaultValue,
    value,
    isReadOnly,
    name,
    onChange,
    autoStack = false,
    ...rest
  } = props;
  const defaultSelected = getDefaultVal(children, value || defaultValue);
  const [selected, setSelected] = useState<string>(defaultSelected);
  const overflowsRef = useRef<Record<string, RadioOverflowInfo>>({});
  const [hasOverflow, setHasOverflow] = useState(false);

  // Handle controlled behavior - update state when value prop changes
  useEffect(() => {
    if (value !== undefined) {
      setSelected(value);
    }
  }, [value]);

  // Handle overflow detection and auto-stack if needed
  const handleOverflow = useCallback(
    (radioValue: string, overflow: RadioOverflowInfo) => {
      overflowsRef.current = {
        ...overflowsRef.current,
        [radioValue]: overflow,
      };

      // Check if any radio has overflow
      const anyOverflow = Object.values(overflowsRef.current).some(
        o => o.vertical || o.horizontal,
      );

      if (anyOverflow !== hasOverflow) {
        setHasOverflow(anyOverflow);
      }
    },
    [hasOverflow],
  );

  // Determine effective orientation (auto-stack if needed)
  const effectiveOrientation =
    autoStack && orientation === "horizontal" && hasOverflow
      ? "vertical"
      : orientation;

  const validationClasses = useValidationClasses({ isInvalid });
  const radioClasses = {
    "--is-disabled": isDisabled,
    "--is-required": typeof isRequired === "boolean" && isRequired,
    "--is-optional": typeof isRequired === "boolean" && !isRequired,
    [`--is-${effectiveOrientation}`]: true,
    [className || ""]: true,
  };
  const radioGroupClasses = classNames(
    "mobius",
    "mobius-radio-group",
    radioClasses,
    validationClasses,
  );
  const radioWrapperClasses = classNames("mobius-radio__wrapper", {
    [`--is-${effectiveOrientation}`]: true,
  });
  const labelClasses = classNames(radioClasses, validationClasses);
  const errorMessageId = useId();
  const defaultNameAttrId = useId();
  const nameAttribute = name || defaultNameAttrId;
  const shouldErrorMessageShow = errorMessage ? errorMessageId : undefined;
  const describedBy = spaceDelimitedList([
    shouldErrorMessageShow,
    props["aria-describedby"],
  ]);
  const labelId = useId();

  return (
    <div
      {...rest}
      aria-describedby={describedBy}
      aria-disabled={isDisabled}
      aria-errormessage={shouldErrorMessageShow}
      aria-invalid={isInvalid}
      aria-label={props["aria-label"]}
      aria-labelledby={props["aria-labelledby"] || labelId}
      aria-orientation={effectiveOrientation}
      aria-readonly={isReadOnly}
      aria-required={isRequired}
      ref={ref}
      className={radioGroupClasses}
      role="radiogroup"
    >
      <Stack gap="xs">
        {label && (
          <Label htmlFor={name} id={labelId} className={labelClasses}>
            {label}
          </Label>
        )}
        <div className={radioWrapperClasses}>
          {Children.map(children, child => {
            if (isValidElement(child)) {
              const childValue = (child.props as { value?: string }).value;
              return cloneElement(
                child as ReactElement,
                {
                  orientation: effectiveOrientation,
                  groupDisabled: isDisabled,
                  name: nameAttribute,
                  selected: selected || defaultSelected, // in case state does not update
                  setSelected,
                  isRequired,
                  "aria-describedby": describedBy,
                  onChange,
                  onOverflow: childValue
                    ? (overflow: RadioOverflowInfo) =>
                        handleOverflow(childValue, overflow)
                    : undefined,
                } as Record<string, unknown>,
              );
            }

            return child;
          })}
        </div>
        <ErrorMessage id={errorMessageId} errorMessage={errorMessage} />
      </Stack>
    </div>
  );
};

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