import { dynamic } from "@solidjs/web";
import { createSignal, omit } from "solid-js";
import { createControlledState } from "../../internals/create-controlled-state.jsx";
import { readOption } from "../../internals/maybe-accessor.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { createTabs } from "../create-tabs.jsx";
import { TabsRootContext } from "../tabs-root-context.jsx";
// Locate the DOM elements backing the previous and next selected values in
// the tab map. Returns `null` slots when a value isn't registered yet (e.g. a
// tab added + selected in the same update).
function findTabElements(map, oldValue, newValue) {
  let oldTab = null;
  let newTab = null;
  for (const [tabElement, tabMetadata] of map.entries()) {
    if (tabMetadata == null) continue;
    const tabValue = tabMetadata.value ?? tabMetadata.index;
    if (oldValue === tabValue) oldTab = tabElement;
    if (newValue === tabValue) newTab = tabElement;
    if (oldTab && newTab) break;
  }
  return {
    oldTab,
    newTab
  };
}

// Dynamic-tabs fallback: when an element isn't registered yet, infer
// direction from the ordering of comparable values. Matches React's
// value-based fallback.
function directionFromValues(oldValue, newValue, orientation, atLeastOneFound) {
  const comparable = atLeastOneFound && (typeof oldValue === "number" || typeof oldValue === "string") && typeof oldValue === typeof newValue;
  if (!comparable) return "none";
  // `comparable` guarantees old/new are the same primitive type; the cast
  // just re-states that for the comparison operator.
  const after = newValue > oldValue;
  if (orientation === "horizontal") {
    return after ? "right" : "left";
  }
  return after ? "down" : "up";
}

// Direction inferred from the on-screen positions of the two tabs.
function directionFromRects(oldRect, newRect, orientation) {
  if (orientation === "horizontal") {
    if (newRect.left < oldRect.left) return "left";
    if (newRect.left > oldRect.left) return "right";
  } else {
    if (newRect.top < oldRect.top) return "up";
    if (newRect.top > oldRect.top) return "down";
  }
  return "none";
}
/**
 * Groups a `<Tabs.List>` + a series of `<Tabs.Panel>`s into a single coordinated
 * tabs widget. Renders a `<div>` by default. Holds selection state, the DOM
 * map of tabs (for activation-direction math + `aria-labelledby`), and the
 * mounted-panel registry (for `aria-controls`).
 *
 * Solid 2 notes:
 *
 * - Provider lives INSIDE `get children()` of `params.props` so the root's
 *   attribute getters stay owned by this setup scope (matches the radio-group /
 *   progress-root pattern).
 * - Activation direction is computed lazily inside `onValueChange`; we also
 *   track the previous value so the `data-activation-direction` attribute can
 *   roll over on the transition. React Base UI recomputes it during render to
 *   avoid a one-frame flash — Solid 2's render-effect timing makes that
 *   unnecessary: the attribute projection runs in a `createRenderEffect` after
 *   the state memo settles, so descendants see the correct direction as soon
 *   as `value` flips.
 */
export function TabsRoot(props) {
  // Controlled / uncontrolled value state (Base UI `value` / `defaultValue`
  // contract): the `value` prop wins while its resolved value is defined
  // (`null` is a controlled "nothing selected"); otherwise an internal
  // signal (seeded once from `defaultValue`, else `0` — React's
  // destructuring default) backs the state.
  const [resolvedValue, setUncontrolledValue] = createControlledState({
    controlled: () => readOption(props.value),
    default: () => readOption(props.defaultValue),
    fallback: 0,
    name: "<Tabs.Root>",
    state: "value"
  });

  // Compose the headless primitive. It owns the value/orientation accessors
  // and the change-event emitter; the wrapper keeps DOM-shaped concerns
  // (tab map, panel-id registry, activation-direction math) since those are
  // inherently structural. Every derivation downstream (tab aria-selected,
  // panel hidden state, indicator, activation-direction math) reads
  // `c.value`, which resolves through the controlled-or-internal accessor.
  const c = createTabs({
    value: resolvedValue,
    orientation: () => readOption(props.orientation) ?? "horizontal"
  });
  const value = c.value;
  const orientation = c.orientation;

  // Tab map owned by this root; `<Tabs.List>`'s composite-list forwards its
  // sorted element-map here via `setTabMap` (plumbed through the context).
  // Stored in a signal so dependent memos (like `selectedTabMetadata`) pick
  // up new registrations.
  const [tabMap, setTabMap] = createSignal(new Map(), {
    ownedWrite: true
  });

  // Activation direction latched inside onValueChange (called from
  // unowned event-handler scope, so `ownedWrite` is correct).
  const [tabActivationDirection, setTabActivationDirection] = createSignal("none", {
    ownedWrite: true
  });
  const computeActivationDirection = (oldValue, newValue) => {
    if (oldValue == null || newValue == null) return "none";
    const {
      oldTab,
      newTab
    } = findTabElements(tabMap(), oldValue, newValue);
    if (!oldTab || !newTab) {
      return directionFromValues(oldValue, newValue, orientation(), Boolean(oldTab || newTab));
    }
    return directionFromRects(oldTab.getBoundingClientRect(), newTab.getBoundingClientRect(), orientation());
  };

  // Change flow mirrors React's `TabsRoot.onValueChange`: compute the
  // activation direction and expose it on the details bag, fire the
  // consumer callback, honor `eventDetails.cancel()`, then latch the
  // direction + write the uncontrolled state (the write is ignored for
  // reads while the `value` prop is controlled — the consumer's signal
  // write is the source of truth).
  const onValueChange = (newValue, eventDetails) => {
    const currentValue = value();
    const direction = computeActivationDirection(currentValue, newValue);
    eventDetails.activationDirection = direction;
    // Internal state is `TabsTabValue` (unknown); the `Value` generic is a
    // typed convenience for callers, so cast at this boundary.
    props.onValueChange?.(newValue, eventDetails);
    if (eventDetails.isCanceled) return;
    setTabActivationDirection(direction);
    setUncontrolledValue(newValue);
  };

  // Mounted panels registry — panels call `registerMountedTabPanel(value, id)`
  // on mount so tabs can wire `aria-controls={panelId}`. Kept as a plain
  // `Map` with a tick signal driving consumer memos.
  const mountedPanels = new Map();
  const [panelTick, setPanelTick] = createSignal(0, {
    ownedWrite: true
  });
  const bumpPanels = () => {
    setPanelTick(t => t + 1);
  };
  const registerMountedTabPanel = (panelValue, panelId) => {
    if (mountedPanels.get(panelValue) === panelId) return;
    mountedPanels.set(panelValue, panelId);
    bumpPanels();
  };
  const unregisterMountedTabPanel = (panelValue, panelId) => {
    if (mountedPanels.get(panelValue) !== panelId) return;
    mountedPanels.delete(panelValue);
    bumpPanels();
  };
  const getTabPanelIdByValue = tabValue => {
    panelTick();
    return mountedPanels.get(tabValue);
  };
  const getTabIdByPanelValue = panelValue => {
    const map = tabMap();
    for (const metadata of map.values()) {
      if (panelValue === metadata?.value) return metadata?.id;
    }
    return undefined;
  };
  const getTabElementBySelectedValue = selectedValue => {
    if (selectedValue === undefined) return null;
    const map = tabMap();
    for (const [tabElement, metadata] of map.entries()) {
      if (metadata == null) continue;
      const tabValue = metadata.value ?? metadata.index;
      if (selectedValue === tabValue) return tabElement;
    }
    return null;
  };
  const contextValue = {
    value,
    onValueChange,
    orientation,
    tabActivationDirection,
    getTabElementBySelectedValue,
    getTabIdByPanelValue,
    getTabPanelIdByValue,
    registerMountedTabPanel,
    unregisterMountedTabPanel,
    setTabMap,
    present: true
  };
  const userClassStyle = resolveClassStyle(props, () => ({
    orientation: orientation(),
    tabActivationDirection: tabActivationDirection()
  }));
  const userHtmlProps = omit(props, "as", "class", "style", "attrs", "children", "defaultValue", "onValueChange", "orientation", "value");
  const internalProps = {
    get "data-orientation"() {
      return orientation();
    },
    get "data-activation-direction"() {
      return tabActivationDirection();
    }
  };
  const merged = mergeRootProps(mergeRootProps(internalProps, userClassStyle), userHtmlProps);
  const Tag = dynamic(() => props.as ?? "div");
  return <Tag {...merged}>
			<TabsRootContext value={contextValue}>{props.children}</TabsRootContext>
		</Tag>;
}
//# sourceMappingURL=tabs-root.jsx.map
