import { useStoreState } from "@ariakit/react-store";
import {
  useEvent,
  createElement,
  createHook,
  forwardRef,
  memo,
} from "@ariakit/react-utils";
import type { Props } from "@ariakit/react-utils";
import type { ChangeEvent, ElementType } from "react";
import type { RadioOptions } from "../radio/radio.tsx";
import { useRadio } from "../radio/radio.tsx";
import { useFormItemContext } from "./form-context.tsx";
import type { FormControlOptions } from "./form-control.tsx";
import { useFormControl } from "./form-control.tsx";

const TagName = "input" satisfies ElementType;
type TagName = typeof TagName;

/**
 * Returns props to create a `FormRadio` component.
 * @see https://ariakit.com/components/form
 * @example
 * ```jsx
 * const store = useFormStore({ defaultValues: { char: "a" } });
 * const a = useFormRadio({ store, name: store.names.char, value: "a" });
 * const b = useFormRadio({ store, name: store.names.char, value: "b" });
 * const c = useFormRadio({ store, name: store.names.char, value: "c" });
 * <Form store={store}>
 *   <FormRadioGroup>
 *     <FormGroupLabel>Favorite character</FormGroupLabel>
 *     <Role {...a} />
 *     <Role {...b} />
 *     <Role {...c} />
 *   </FormRadioGroup>
 * </Form>
 * ```
 */
export const useFormRadio = createHook<TagName, FormRadioOptions>(
  function useFormRadio({ store, name: nameProp, value, ...props }) {
    const { store: form, name } = useFormItemContext({
      store,
      name: nameProp,
      component: "FormRadio",
    });
    const onChangeProp = props.onChange;

    const onChange = useEvent((event: ChangeEvent<HTMLInputElement>) => {
      onChangeProp?.(event);
      if (event.defaultPrevented) return;
      form.setValue(name, value);
    });

    const checkedProp = props.checked;
    const checked = useStoreState(
      form,
      ["values"],
      () => checkedProp ?? form.getValue(name) === value,
    );

    props = {
      ...props,
      checked,
      onChange,
    };

    props = useRadio({ name, value, ...props });

    props = useFormControl({
      store: form,
      name,
      "aria-labelledby": undefined,
      ...props,
    });

    return props;
  },
);

/**
 * Renders a radio button as a form control. This component must be wrapped in a
 * [`FormRadioGroup`](https://ariakit.com/reference/form-radio-group) along with
 * other radio buttons sharing the same
 * [`name`](https://ariakit.com/reference/form-radio#name).
 * @see https://ariakit.com/components/form
 * @example
 * ```jsx {10-12}
 * const form = useFormStore({
 *   defaultValues: {
 *     char: "a",
 *   },
 * });
 *
 * <Form store={form}>
 *   <FormRadioGroup>
 *     <FormGroupLabel>Favorite character</FormGroupLabel>
 *     <FormRadio name={form.names.char} value="a" />
 *     <FormRadio name={form.names.char} value="b" />
 *     <FormRadio name={form.names.char} value="c" />
 *   </FormRadioGroup>
 * </Form>
 * ```
 */
export const FormRadio = memo(
  forwardRef(function FormRadio(props: FormRadioProps) {
    const htmlProps = useFormRadio(props);
    return createElement(TagName, htmlProps);
  }),
);

export interface FormRadioOptions<T extends ElementType = TagName>
  extends FormControlOptions<T>, Omit<RadioOptions<T>, "store" | "name"> {}

export type FormRadioProps<T extends ElementType = TagName> = Props<
  T,
  FormRadioOptions<T>
>;
