import {
  useId,
  useSafeLayoutEffect,
  createElement,
  createHook,
  forwardRef,
} from "@ariakit/react-utils";
import type { Options, Props } from "@ariakit/react-utils";
import type { ElementType } from "react";
import { useContext } from "react";
import { DialogDescriptionContext } from "./dialog-context.tsx";
import type { DialogStore } from "./dialog-store.ts";

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

/**
 * Returns props to create a `DialogDescription` component. This hook must be
 * used in a component that's wrapped with `Dialog` so the `aria-describedby`
 * prop is properly set on the dialog element.
 * @see https://ariakit.com/components/dialog
 * @example
 * ```jsx
 * // This component must be wrapped with Dialog
 * const props = useDialogDescription();
 * <Role {...props}>Description</Role>
 * ```
 */
export const useDialogDescription = createHook<
  TagName,
  DialogDescriptionOptions
>(function useDialogDescription({ store, ...props }) {
  const setDescriptionId = useContext(DialogDescriptionContext);
  const id = useId(props.id);

  useSafeLayoutEffect(() => {
    setDescriptionId?.(id);
    return () => setDescriptionId?.(undefined);
  }, [setDescriptionId, id]);

  props = {
    ...props,
    id,
  };

  return props;
});

/**
 * Renders a description in a dialog. This component must be wrapped with
 * [`Dialog`](https://ariakit.com/reference/dialog) so the `aria-describedby`
 * prop is properly set on the dialog element.
 * @see https://ariakit.com/components/dialog
 * @example
 * ```jsx {4}
 * const [open, setOpen] = useState(false);
 *
 * <Dialog open={open} onClose={() => setOpen(false)}>
 *   <DialogDescription>Description</DialogDescription>
 * </Dialog>
 * ```
 */
export const DialogDescription = forwardRef(function DialogDescription(
  props: DialogDescriptionProps,
) {
  const htmlProps = useDialogDescription(props);
  return createElement(TagName, htmlProps);
});

export interface DialogDescriptionOptions<
  _T extends ElementType = TagName,
> extends Options {
  /**
   * Object returned by the
   * [`useDialogStore`](https://ariakit.com/reference/use-dialog-store) hook.
   *
   * **Note**: This prop has no effect on this component. The description is
   * linked to the closest [`Dialog`](https://ariakit.com/reference/dialog)
   * component through React context, so it must be rendered inside the dialog
   * for the `aria-describedby` prop to be set on the dialog element.
   */
  store?: DialogStore;
}

export type DialogDescriptionProps<T extends ElementType = TagName> = Props<
  T,
  DialogDescriptionOptions<T>
>;
