import {
  type Middleware,
  type Placement,
  type ReferenceElement,
  type Strategy,
  autoUpdate,
  computePosition,
} from "@floating-ui/dom";
import { type Accessor, createEffect, createSignal, onCleanup } from "solid-js";
import type { JSX } from "solid-js";

/** Options accepted by {@link createFloating}. */
export interface CreateFloatingOptions {
  /** Reference/anchor element. Accepts a real DOM element or a virtual element with getBoundingClientRect. */
  anchor: Accessor<ReferenceElement | null>;
  /** The floating element to position. */
  floating: Accessor<HTMLElement | null>;
  /** Desired placement. @default "bottom" */
  placement?: Accessor<Placement>;
  /** Floating UI middleware (flip, shift, offset, arrow, etc.). */
  middleware?: Accessor<Middleware[]>;
  /** CSS positioning strategy. @default "absolute" */
  strategy?: Accessor<Strategy>;
  /** Only compute position when true. @default () => true */
  open?: Accessor<boolean>;
}

/** Reactive floating position state returned by {@link createFloating}. */
export interface FloatingState {
  /** Computed x position in px. */
  x: Accessor<number>;
  /** Computed y position in px. */
  y: Accessor<number>;
  /** Actual placement after middleware (may differ from requested after flip). */
  placement: Accessor<Placement>;
  /** Ready-to-spread CSS: position, top, left. */
  style: Accessor<JSX.CSSProperties>;
}

/**
 * Thin reactive wrapper around `@floating-ui/dom`.
 *
 * Computes and auto-updates the position of a floating element
 * relative to an anchor. Cleans up automatically when the reactive
 * owner is disposed.
 */
export function createFloating(options: CreateFloatingOptions): FloatingState {
  const [x, setX] = createSignal(0);
  const [y, setY] = createSignal(0);
  const [currentPlacement, setCurrentPlacement] = createSignal<Placement>(
    options.placement?.() ?? "bottom",
  );

  /** Resolve the CSS positioning strategy, defaulting to "absolute". */
  const getStrategy = () => options.strategy?.() ?? "absolute";
  /** Whether positioning should be active, defaulting to true. */
  const isOpen = () => options.open?.() ?? true;

  /** Runs computePosition and writes results into signals. */
  const update = async () => {
    const anchor = options.anchor();
    const floating = options.floating();
    if (!anchor || !floating) return;

    const mw = options.middleware?.();
    const result = await computePosition(anchor, floating, {
      placement: options.placement?.() ?? "bottom",
      ...(mw ? { middleware: mw } : {}),
      strategy: getStrategy(),
    });

    setX(result.x);
    setY(result.y);
    setCurrentPlacement(result.placement);
  };

  createEffect(() => {
    const anchor = options.anchor();
    const floating = options.floating();

    if (!anchor || !floating || !isOpen()) return;

    update();

    const cleanup = autoUpdate(anchor, floating, update);
    onCleanup(cleanup);
  });

  const style: Accessor<JSX.CSSProperties> = () => ({
    position: getStrategy(),
    top: `${y()}px`,
    left: `${x()}px`,
  });

  return { x, y, placement: currentPlacement, style };
}
