import type { Middleware, Placement } from "@floating-ui/dom";
import { flip, offset, shift } from "@floating-ui/dom";
import type { Accessor, JSX } from "solid-js";
import { createSignal, createUniqueId, splitProps } from "solid-js";
import { createDisclosureState } from "../../primitives/create-disclosure-state";
import { createFloating } from "../../primitives/create-floating";
import { createListNavigation } from "../../primitives/create-list-navigation";
import { ComboboxContent } from "./combobox-content";
import {
  ComboboxContextProvider,
  InternalComboboxContextProvider,
  useComboboxContext,
} from "./combobox-context";
import type { InternalComboboxContextValue } from "./combobox-context";
import { ComboboxEmpty } from "./combobox-empty";
import { ComboboxGroup } from "./combobox-group";
import { ComboboxGroupLabel } from "./combobox-group-label";
import { ComboboxInput } from "./combobox-input";
import { ComboboxItem } from "./combobox-item";
import { ComboboxTrigger } from "./combobox-trigger";

export interface ComboboxRootProps {
  /** Ordered list of active (already filtered) item values. */
  items: string[];
  /** Controlled selected value. */
  value?: string | undefined;
  /** Initial selected value (uncontrolled). */
  defaultValue?: string | undefined;
  /** Called when value changes. */
  onValueChange?: ((value: string) => void) | undefined;
  /** Controlled open state. */
  open?: boolean | undefined;
  /** Initial open state (uncontrolled). */
  defaultOpen?: boolean | undefined;
  /** Called when open state changes. */
  onOpenChange?: ((open: boolean) => void) | undefined;
  /** Controlled input text value. */
  inputValue?: string | undefined;
  /** Called when the input text changes. */
  onInputChange?: ((value: string) => void) | undefined;
  /** Allow selecting a value not in the items list. */
  allowCustomValue?: boolean | undefined;
  /** Resolve value to display label. */
  getLabel?: ((value: string) => string) | undefined;
  /** Whether the combobox is disabled. */
  disabled?: boolean | undefined;
  /** Whether selection is required. */
  required?: boolean | undefined;
  /** Form field name. */
  name?: string | undefined;
  children: JSX.Element;
}

/** Creates the three core primitives (disclosure, navigation, floating). */
function createComboboxPrimitives(
  local: ComboboxRootProps,
  inputRef: Accessor<HTMLElement | null>,
  contentRef: Accessor<HTMLElement | null>,
  setInternalInputValue: (v: string) => void,
  baseId: string,
) {
  const disclosure = createDisclosureState({
    get open() { return local.open; },
    get defaultOpen() { return local.defaultOpen; },
    get onOpenChange() { return local.onOpenChange; },
  });

  const navigation = createListNavigation({
    items: () => local.items,
    mode: "selection",
    value: local.value !== undefined ? () => local.value : undefined,
    defaultValue: local.defaultValue,
    typeahead: false,
    onValueChange: (v) => {
      local.onValueChange?.(v);
      setInternalInputValue(local.getLabel ? local.getLabel(v) : v);
      disclosure.close();
    },
    getLabel: local.getLabel,
    baseId,
  });

  const floating = createFloating({
    anchor: inputRef,
    floating: contentRef,
    placement: (() => "bottom-start") as Accessor<Placement>,
    middleware: (() => [offset(8), flip(), shift({ padding: 8 })]) as Accessor<Middleware[]>,
    open: disclosure.isOpen,
  });

  return { disclosure, navigation, floating };
}

/**
 * Root component for Combobox. Manages open state, selection, input value,
 * floating positioning, and keyboard navigation via context.
 */
export function ComboboxRoot(props: ComboboxRootProps): JSX.Element {
  const [local] = splitProps(props, [
    "items", "value", "defaultValue", "onValueChange",
    "open", "defaultOpen", "onOpenChange",
    "inputValue", "onInputChange", "allowCustomValue",
    "getLabel", "disabled", "required", "name", "children",
  ]);

  const inputId = createUniqueId();
  const contentId = createUniqueId();
  const baseId = createUniqueId();

  const [inputRef, setInputRef] = createSignal<HTMLInputElement | null>(null);
  const [contentRef, setContentRef] = createSignal<HTMLElement | null>(null);
  const [internalInputValue, setInternalInputValue] = createSignal("");
  const resolveInputValue = () => local.inputValue ?? internalInputValue();

  const { disclosure, navigation, floating } = createComboboxPrimitives(
    local, inputRef as Accessor<HTMLElement | null>, contentRef, setInternalInputValue, baseId,
  );

  const ctx: InternalComboboxContextValue = {
    isOpen: disclosure.isOpen, open: disclosure.open,
    close: disclosure.close, toggle: disclosure.toggle,
    navigation, inputRef, setInputRef, contentRef, setContentRef,
    selectedValue: navigation.selectedValue,
    onSelect: (value: string) => navigation.getItemProps(value).onClick(),
    disabled: () => local.disabled ?? false,
    required: () => local.required ?? false,
    name: () => local.name, contentId, inputId,
    items: () => local.items, inputValue: resolveInputValue,
    setInputValue: setInternalInputValue, onInputChange: local.onInputChange,
    allowCustomValue: () => local.allowCustomValue ?? false,
    onCustomValue: (v: string) => { local.onValueChange?.(v); disclosure.close(); },
    floatingStyle: floating.style,
  };

  return (
    <InternalComboboxContextProvider value={ctx}>
      <ComboboxContextProvider
        value={{ value: navigation.selectedValue, open: disclosure.isOpen, inputValue: resolveInputValue }}
      >
        {local.children}
      </ComboboxContextProvider>
    </InternalComboboxContextProvider>
  );
}

/** Compound Combobox component with all sub-components as static properties. */
export const Combobox = Object.assign(ComboboxRoot, {
  Input: ComboboxInput,
  Trigger: ComboboxTrigger,
  Content: ComboboxContent,
  Item: ComboboxItem,
  Empty: ComboboxEmpty,
  Group: ComboboxGroup,
  GroupLabel: ComboboxGroupLabel,
  useContext: useComboboxContext,
});
