import { cloneElement, forwardRef, isValidElement, useId } from "react";

import { omit } from "../../utils";
import {
  StyledContainer,
  StyledIndicator,
  StyledItem,
  StyledItemContainer,
  StyledLabel,
  StyledRoot,
} from "./radio-group.styled";

import type { UrlObject } from "url";

export interface OptionBase {
  value: string;
  id?: string;
}

export interface Option extends OptionBase {
  label: string;
}

export interface LinkOption extends OptionBase {
  label: React.ReactElement<{
    href: string | UrlObject;
    children?: React.ReactNode;
  }>;
}

export interface RadioGroupProps extends React.ComponentProps<typeof StyledRoot> {
  /**
   * The options of the radio group
   */
  options: Array<Option> | Array<LinkOption>;

  /**
   * Sets the default value of the radio group
   */
  defaultValue?: string;
  /**
   * [Radix Documentation](https://www.radix-ui.com/primitives/docs/components/radio-group#item)
   */
  itemProps?: Omit<React.ComponentProps<typeof StyledItem>, "value" | "id" | "asChild">;
  /**
   * [Radix Documentation](https://www.radix-ui.com/primitives/docs/components/radio-group#indicator)
   */
  indicatorProps?: React.ComponentProps<typeof StyledIndicator>;
  /**
   * Change the default rendered element for the one passed as a child, merging their props and behavior.
   */
  asChild?: boolean;
}

/**
 * Based on [Radix Primitives Radio Group](https://www.radix-ui.com/primitives/docs/components/radio-group).
 *
 * A radio group implementation that can render options as links as well
 */
export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>((props, ref) => {
  const { options, itemProps, indicatorProps, ...rest } = props;
  const uuid = useId();

  return (
    <StyledRoot ref={ref} {...rest}>
      {options.map((option) => {
        const isLinkOption = isValidElement(option.label);
        return (
          <StyledItemContainer
            key={uuid + ("id" in option ? option.id : option.value)}
            asChild={isLinkOption}
          >
            <StyledItem
              value={option.value}
              id={uuid + ("id" in option ? option.id : option.value)}
              asChild={isLinkOption}
              {...omit(itemProps, ["value", "id", "asChild"])}
            >
              {isLinkOption ? (
                cloneElement(
                  // @ts-expect-error isLinkOption guarantees type of option.label
                  option.label,
                  {},
                  <>
                    <StyledIndicator {...indicatorProps}>
                      {/* @ts-expect-error isLinkOption guarantees type of option.label */}
                      {option.label.props.children}
                    </StyledIndicator>
                    <StyledLabel htmlFor={uuid + ("id" in option ? option.id : option.value)}>
                      {/* @ts-expect-error isLinkOption guarantees type of option.label */}
                      {option.label.props.children}
                    </StyledLabel>
                  </>
                )
              ) : (
                <>
                  <StyledIndicator {...indicatorProps}>{option.label}</StyledIndicator>
                  <StyledLabel htmlFor={uuid + ("id" in option ? option.id : option.value)}>
                    {option.label}
                  </StyledLabel>
                </>
              )}
            </StyledItem>
          </StyledItemContainer>
        );
      })}
    </StyledRoot>
  );
});

RadioGroup.displayName = "RadioGroup";

export const RadioGroupRootSelector = StyledRoot.toString();
export const RadioGroupLabelSelector = StyledLabel.toString();
export const RadioGroupItemContainerSelector = StyledContainer.toString();
export const RadioGroupIndicatorSelector = StyledIndicator.toString();
export const RadioGroupItemSelector = StyledItem.toString();
