import { useStoreState } from "@ariakit/react-store";
import { createElement, forwardRef } from "@ariakit/react-utils";
import type { Props } from "@ariakit/react-utils";
import { toArray } from "@ariakit/utils";
import type { ElementType } from "react";
import { useMemo } from "react";
import type {
  CompositeRendererBaseItemProps,
  CompositeRendererItem,
  CompositeRendererItemObject,
  CompositeRendererItemProps,
  CompositeRendererOptions,
} from "../composite/composite-renderer.tsx";
import {
  getCompositeRendererItem,
  getCompositeRendererItemId,
  useCompositeRenderer,
} from "../composite/composite-renderer.tsx";
import { useSelectContext } from "./select-context.tsx";
import type { SelectStore, SelectStoreValue } from "./select-store.ts";

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

interface ItemObject extends CompositeRendererItemObject {
  value?: string;
}

type Item = ItemObject | CompositeRendererItem;

type BaseItemProps = CompositeRendererBaseItemProps;

type ItemProps<
  T extends Item,
  P extends BaseItemProps = BaseItemProps,
> = CompositeRendererItemProps<T, P>;

function getItemObject(item: Item): ItemObject {
  if (!item || typeof item !== "object") {
    return { value: `${item}` };
  }
  return item;
}

function findIndicesByValue(
  items: readonly Item[],
  value: SelectStoreValue,
): number[] {
  const values = toArray(value);
  const indices: number[] = [];

  for (const [index, item] of items.entries()) {
    if (indices.length === values.length) break;

    const object = getItemObject(item);

    if (object.value != null && values.includes(object.value)) {
      indices.push(index);
    } else if (object.items?.length) {
      const childIndices = findIndicesByValue(object.items, value);
      if (childIndices.length) {
        indices.push(index);
      }
    }
  }

  return indices;
}

function useSelectRenderer<T extends Item = any>({
  store,
  persistentIndices: persistentIndicesProp,
  items: itemsProp,
  value: valueProp,
  ...props
}: SelectRendererProps<T>) {
  const context = useSelectContext();
  store = store || context;

  const items = useStoreState(store, ["mounted", "items"], (state) => {
    if (!state) return itemsProp;
    if (!state.mounted) return 0;
    return itemsProp ?? (state.items as T[]);
  });

  const value = useStoreState(
    store,
    ["value"],
    (state) => valueProp ?? state?.value,
  );

  const valueIndices = useMemo(() => {
    if (!items) return [];
    if (value == null) return [];
    if (typeof items === "number") return [];
    if (!items.length) return [];
    return findIndicesByValue(items, value);
  }, [items, value]);

  const persistentIndices = useMemo(() => {
    if (persistentIndicesProp) {
      return [...persistentIndicesProp, ...valueIndices];
    }
    return valueIndices;
  }, [valueIndices, persistentIndicesProp]);

  return useCompositeRenderer({
    store: store as CompositeRendererOptions["store"],
    items,
    persistentIndices,
    ...props,
  });
}

/** @deprecated Use `ComboboxRenderer` instead. */
export const SelectRenderer = forwardRef(function SelectRenderer<
  T extends Item = any,
>(props: SelectRendererProps<T>) {
  const htmlProps = useSelectRenderer(props);
  return createElement(TagName, htmlProps);
});

/** @deprecated Use `getComboboxRendererItem` instead. */
export const getSelectRendererItem: typeof getCompositeRendererItem =
  getCompositeRendererItem;

/** @deprecated Use `getComboboxRendererItemId` instead. */
export const getSelectRendererItemId: typeof getCompositeRendererItemId =
  getCompositeRendererItemId;

/** @deprecated Use `ComboboxRendererItemObject` instead. */
export type SelectRendererItemObject = ItemObject;
/** @deprecated Use `ComboboxRendererItem` instead. */
export type SelectRendererItem = Item;
/** @deprecated Use `ComboboxRendererBaseItemProps` instead. */
export type SelectRendererBaseItemProps = BaseItemProps;
/** @deprecated Use `ComboboxRendererItemProps` instead. */
export type SelectRendererItemProps<
  T extends Item,
  P extends BaseItemProps = BaseItemProps,
> = ItemProps<T, P>;

/** @deprecated Use `ComboboxRendererOptions` instead. */
export interface SelectRendererOptions<T extends Item = any> extends Omit<
  CompositeRendererOptions<T>,
  "store"
> {
  /**
   * Object returned by the
   * [`useSelectStore`](https://ariakit.com/reference/use-select-store) hook. If
   * not provided, the closest [Select](https://ariakit.com/components/select)
   * component's context will be used.
   *
   * The store [`items`](https://ariakit.com/reference/use-select-store#items)
   * state will be used to render the items if the
   * [`items`](https://ariakit.com/reference/select-items#items) prop is not
   * provided.
   */
  store?: SelectStore;
  /**
   * The current value of the select. This will ensure the item with the given
   * value is rendered even if it's not in the viewport, so it can be
   * automatically focused when the select popover is opened. If not provided,
   * the value will be read from the store.
   */
  value?: SelectStoreValue;
}

/** @deprecated Use `ComboboxRendererProps` instead. */
export interface SelectRendererProps<T extends Item = any> extends Props<
  TagName,
  SelectRendererOptions<T>
> {}
