import { dynamic } from "@solidjs/web";
import { createMemo, createRenderEffect, createSignal, omit, onCleanup, Show, untrack } from "solid-js";
import { useCspNonce } from "../../csp-provider/csp-context.jsx";
import { isHydrating } from "../../internals/is-hydrating.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { useTabsListContext } from "../tabs-list-context.jsx";
import { useTabsRootContext } from "../tabs-root-context.jsx";
import { tabsIndicatorPrehydrationScript } from "./prehydration-script.jsx";
/**
 * Visual indicator that tracks the position/size of the currently-active tab.
 * Renders a `<span role="presentation">` with `--active-tab-*` CSS custom
 * properties the consumer can wire into their own styles.
 *
 * Deviations from React Base UI:
 *
 * - Measurement runs on mount + whenever the active-tab value or the list
 *   element changes. The list-level `ResizeObserver` (set up here) bumps a
 *   tick signal on every observed resize so the measurement memo re-runs.
 */
export function TabsIndicator(props) {
  const root = useTabsRootContext();
  const list = useTabsListContext();
  const nonce = useCspNonce();

  // Tick signal bumped on every observed resize so the measurement memo
  // re-runs. `ownedWrite` because the ResizeObserver callback fires from
  // unowned scope.
  const [resizeTick, setResizeTick] = createSignal(0, {
    ownedWrite: true
  });
  let resizeObserver = null;
  if (typeof ResizeObserver !== "undefined") {
    resizeObserver = new ResizeObserver(() => {
      setResizeTick(t => t + 1);
    });
  }
  onCleanup(() => {
    resizeObserver?.disconnect();
    resizeObserver = null;
    currentListElement = null;
    currentActiveTab = null;
  });

  // Re-observe the list element whenever it swaps. The memo also observes
  // the active tab directly — a mutation in either triggers a re-measure.
  let currentListElement = null;
  // Tracks the active tab currently observed so `measure()` can unobserve the
  // previous one before observing a new one (stale tabs would otherwise keep
  // firing redundant re-measures). Mirrors the list-element swap pattern below.
  let currentActiveTab = null;
  createRenderEffect(() => list.tabsListElement(), next => {
    const observer = resizeObserver;
    if (!observer) return;
    if (currentListElement && currentListElement !== next) {
      observer.unobserve(currentListElement);
    }
    if (next && next !== currentListElement) {
      observer.observe(next);
    }
    currentListElement = next;
  });
  const emptyMeasurement = () => ({
    isTabSelected: false,
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    width: 0,
    height: 0
  });
  const measure = () => {
    resizeTick();
    const value = root.value();
    const listEl = list.tabsListElement();
    if (value == null || listEl == null) {
      return emptyMeasurement();
    }
    const activeTab = root.getTabElementBySelectedValue(value);
    if (activeTab == null) {
      return {
        ...emptyMeasurement(),
        isTabSelected: true
      };
    }
    if (currentActiveTab && currentActiveTab !== activeTab) {
      resizeObserver?.unobserve(currentActiveTab);
    }
    resizeObserver?.observe(activeTab);
    currentActiveTab = activeTab;
    const tabRect = activeTab.getBoundingClientRect();
    const listRect = listEl.getBoundingClientRect();
    const scaleX = listEl.offsetWidth > 0 ? listRect.width / listEl.offsetWidth : 1;
    const scaleY = listEl.offsetHeight > 0 ? listRect.height / listEl.offsetHeight : 1;
    const hasNonZeroScale = Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON;
    let left;
    let top;
    if (hasNonZeroScale) {
      left = (tabRect.left - listRect.left) / scaleX + listEl.scrollLeft - listEl.clientLeft;
      top = (tabRect.top - listRect.top) / scaleY + listEl.scrollTop - listEl.clientTop;
    } else {
      left = activeTab.offsetLeft;
      top = activeTab.offsetTop;
    }
    const width = activeTab.offsetWidth;
    const height = activeTab.offsetHeight;
    const right = listEl.scrollWidth - left - width;
    const bottom = listEl.scrollHeight - top - height;
    return {
      isTabSelected: true,
      left,
      right,
      top,
      bottom,
      width,
      height
    };
  };

  // Measurement must run in a render effect, not during render: tab elements
  // register with `<CompositeList>` in their own render effects, and the list
  // forwards the sorted map to `<Tabs.Root>` in a layout-timed render effect.
  // A render-phase memo would always measure one frame early (empty tab map).
  const [measurement, setMeasurement] = createSignal(emptyMeasurement(), {
    ownedWrite: true
  });
  createRenderEffect(() => {
    resizeTick();
    const value = root.value();
    const listEl = list.tabsListElement();
    if (value != null && listEl != null) {
      root.getTabElementBySelectedValue(value);
    }
  }, () => {
    // The compute above already tracks every reactive value `measure`
    // reads (`resizeTick`, `root.value`, `list.tabsListElement`, and
    // `getTabElementBySelectedValue`), so this apply-scope call is a
    // snapshot — untrack to avoid `[STRICT_READ_UNTRACKED]`.
    setMeasurement(untrack(measure));
  });
  const activeTabPosition = createMemo(() => {
    const m = measurement();
    if (!m.isTabSelected) return null;
    return {
      left: m.left,
      right: m.right,
      top: m.top,
      bottom: m.bottom
    };
  });
  const activeTabSize = createMemo(() => {
    const m = measurement();
    if (!m.isTabSelected) return null;
    return {
      width: m.width,
      height: m.height
    };
  });

  // Keep the memos referenced for any inline-headless consumer surface;
  // the wrapper itself doesn't read activeTabPosition/Size as data attrs
  // (they were always projected as null in the React-parity mapping).
  void activeTabPosition;
  void activeTabSize;
  const indicatorStyle = createMemo(() => {
    const m = measurement();
    if (!m.isTabSelected) return undefined;
    return {
      "--active-tab-left": `${m.left}px`,
      "--active-tab-right": `${m.right}px`,
      "--active-tab-top": `${m.top}px`,
      "--active-tab-bottom": `${m.bottom}px`,
      "--active-tab-width": `${m.width}px`,
      "--active-tab-height": `${m.height}px`
    };
  });
  const displayIndicator = () => {
    const m = measurement();
    return m.isTabSelected && m.width > 0 && m.height > 0;
  };
  const userClassStyle = resolveClassStyle(props, () => ({
    activeTabPosition: activeTabPosition(),
    activeTabSize: activeTabSize(),
    orientation: root.orientation(),
    tabActivationDirection: root.tabActivationDirection()
  }));
  const userHtmlProps = omit(props, "as", "class", "style", "attrs", "children", "renderBeforeHydration");
  const internalProps = {
    role: "presentation",
    get style() {
      return indicatorStyle();
    },
    get hidden() {
      return displayIndicator() ? undefined : true;
    },
    get "data-orientation"() {
      return root.orientation();
    },
    get "data-activation-direction"() {
      return root.tabActivationDirection();
    }
  };
  const merged = mergeRootProps(mergeRootProps(internalProps, userClassStyle), userHtmlProps);

  // SSR prehydration: emit the inline `<script>` immediately after the
  // indicator element (it reads `document.currentScript.previousElementSibling`)
  // so it positions the indicator before hydration. Gated on
  // `isHydrating && renderBeforeHydration` — server-only, mirrors React
  // `TabsIndicator.tsx:169-176`. Rendered as a plain element (not a spread of
  // an inline call) so Solid's SSR compiler doesn't thunk it.
  const showScript = () => isHydrating() && props.renderBeforeHydration === true;
  const Tag = dynamic(() => props.as ?? "span");
  return <Show when={root.value() != null}>
			<Tag {...merged} />
			<Show when={showScript()}>
				<script nonce={nonce()} innerHTML={tabsIndicatorPrehydrationScript} />
			</Show>
		</Show>;
}
//# sourceMappingURL=tabs-indicator.jsx.map
