import { dynamic } from "@solidjs/web";
import { createMemo, createSignal, omit, untrack } from "solid-js";
import { activeElement } from "../../internals/dom/active-element.jsx";
import { contains } from "../../internals/dom/contains.jsx";
import { ownerDocument } from "../../internals/dom/owner-document.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { useOpenChangeComplete } from "../../internals/popups/use-open-change-complete.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { createStableCallback } from "../../internals/stable-callback.jsx";
import { useIsoLayoutEffect } from "../../internals/use-iso-layout-effect.jsx";
import { useSwipeDismiss } from "../../internals/use-swipe-dismiss.jsx";
import { useToastProviderContext } from "../provider/toast-provider-context.jsx";
import { ToastRootContext } from "./toast-root-context.jsx";
const TOAST_INDEX = "--toast-index";
const TOAST_OFFSET_Y = "--toast-offset-y";
const TOAST_HEIGHT = "--toast-height";
const SWIPE_MOVEMENT_X = "--toast-swipe-movement-x";
const SWIPE_MOVEMENT_Y = "--toast-swipe-movement-y";
function getToastStackIndices(toasts, id) {
  let visibleIndex = 0;
  let offsetY = 0;
  let domIndex = -1;
  let visibleIdx = -1;
  let offset = 0;
  for (let i = 0; i < toasts.length; i++) {
    const item = toasts[i];
    if (!item) continue;
    const isEnding = item.transitionStatus === "ending";
    if (item.id === id) {
      domIndex = i;
      visibleIdx = isEnding ? -1 : visibleIndex;
      offset = offsetY;
    }
    offsetY += item.height ?? 0;
    if (!isEnding) {
      visibleIndex += 1;
    }
  }
  return {
    domIndex,
    visibleIndex: visibleIdx,
    offsetY: offset
  };
}
export function ToastRoot(props) {
  const store = useToastProviderContext();

  // `equals: false` because the store mutates a toast's `height` /
  // `transitionStatus` IN PLACE (to keep the keyed `<For>` row mounted — see
  // `updateToastInternal`). `find` would otherwise return the same object
  // reference and the memo, with default identity equality, would not
  // re-notify style/`data-*` readers when those fields change. Forcing
  // notification on every queue write keeps the CSS height var and transition
  // attributes live without resurrecting the row-rebuild feedback loop.
  const toast = createMemo(() => {
    const id = props.toast.id;
    return store.toasts().find(t => t.id === id) ?? props.toast;
  }, {
    equals: false
  });
  const stackIndices = createMemo(() => getToastStackIndices(store.toasts(), toast().id));
  const [titleId, setTitleId] = createSignal(undefined, {
    ownedWrite: true
  });
  const [descriptionId, setDescriptionId] = createSignal(undefined, {
    ownedWrite: true
  });
  let rootEl = null;
  const rootRef = {
    get current() {
      return rootEl;
    },
    set current(value) {
      rootEl = value;
    }
  };
  useOpenChangeComplete({
    open: () => toast().transitionStatus !== "ending",
    ref: rootRef,
    onComplete() {
      if (toast().transitionStatus === "ending") {
        store.removeToast(toast().id);
      }
    }
  });

  // Matches React `ToastRoot.recalculateHeight` / Solid v1 `setHeights`.
  const recalculateHeight = createStableCallback(() => () => {
    const el = rootEl;
    if (!el) return;
    const previousHeight = el.style.height;
    el.style.height = "auto";
    const height = el.offsetHeight;
    el.style.height = previousHeight;
    const current = untrack(toast);
    // Use the resolved (untracked) toast id rather than reading the reactive
    // `props.toast` getter directly — `recalculateHeight` runs from effect
    // and ref scopes, where a tracked read trips `[STRICT_READ_UNTRACKED]`.
    // `current.id` equals `props.toast.id` (the memo matches by id).
    store.updateToastInternal(current.id, {
      height,
      ...(current.transitionStatus === "starting" ? {
        transitionStatus: undefined
      } : {})
    });
  });

  // `rootEl` is a plain let — track mount via ref + layout effect keyed on
  // the toast's id. Split-effect applies re-run on every compute
  // RE-EXECUTION (not when the compute's return value changes), so the
  // compute must read a value that only notifies on a genuine id change.
  // Reading `toast().id` directly would subscribe to the `toast` memo, which
  // returns a fresh object on every queue write and thus notifies on each
  // one — and because the apply calls `recalculateHeight` (which WRITES the
  // queue), that is a self-feeding loop. In happy-dom the height is constant
  // so `updateToastInternal`'s no-op guard halts it, but in a real browser
  // sub-pixel measurement oscillation defeats the guard and the effect spins
  // at 100% CPU. Gate through a memo so the apply only fires when the id
  // actually changes (i.e. once, at mount).
  const toastId = createMemo(() => toast().id);
  useIsoLayoutEffect(() => toastId(), () => {
    if (rootEl) recalculateHeight();
  });
  const isHighPriority = () => toast().priority === "high";
  function handleKeyDown(event) {
    if (event.key !== "Escape") return;
    const el = rootEl;
    if (!el) return;
    if (!contains(el, activeElement(ownerDocument(el)))) return;
    store.closeToast(toast().id);
  }
  const initialSwipeDirection = props.swipeDirection ?? ["down", "right"];
  const swipeDirections = Array.isArray(initialSwipeDirection) ? initialSwipeDirection : [initialSwipeDirection];
  const swipeEnabled = swipeDirections.length > 0;
  const swipe = useSwipeDismiss({
    enabled: () => swipeEnabled,
    directions: swipeDirections,
    elementRef: () => rootEl,
    movementCssVars: {
      x: SWIPE_MOVEMENT_X,
      y: SWIPE_MOVEMENT_Y
    },
    onDismiss(_event, _details) {
      store.closeToast(toast().id);
    }
  });
  const visibleIndex = createMemo(() => stackIndices().visibleIndex);
  const contextValue = {
    toast,
    titleId,
    setTitleId,
    descriptionId,
    setDescriptionId,
    expanded: store.expanded,
    visibleIndex,
    recalculateHeight
  };
  const userClassStyle = resolveClassStyle(props, () => ({
    transitionStatus: toast().transitionStatus,
    expanded: store.expanded(),
    limited: Boolean(toast().limited),
    type: toast().type,
    swiping: swipe.swiping(),
    swipeDirection: swipe.swipeDirection()
  }));
  const userHtmlProps = omit(props, "as", "class", "style", "attrs", "toast", "swipeDirection", "children");
  function forwardMouseEventToViewport(type) {
    const vp = store.viewport();
    if (vp) vp.dispatchEvent(new Event(type));
  }
  const internalProps = {
    ref: el => {
      rootEl = el;
      if (el) recalculateHeight();
    },
    onMouseEnter: () => forwardMouseEventToViewport("mouseenter"),
    onMouseLeave: () => forwardMouseEventToViewport("mouseleave"),
    tabIndex: 0,
    "aria-modal": false,
    get role() {
      return isHighPriority() ? "alertdialog" : "dialog";
    },
    get "aria-labelledby"() {
      return titleId() ?? "";
    },
    get "aria-describedby"() {
      return descriptionId() ?? "";
    },
    get "aria-hidden"() {
      return isHighPriority() && !store.focused() ? "true" : undefined;
    },
    onKeyDown: handleKeyDown,
    get onPointerDown() {
      return swipe.getPointerProps().onPointerDown;
    },
    get onPointerMove() {
      return swipe.getPointerProps().onPointerMove;
    },
    get onPointerUp() {
      return swipe.getPointerProps().onPointerUp;
    },
    get onPointerCancel() {
      return swipe.getPointerProps().onPointerCancel;
    },
    get style() {
      const dragStyles = swipe.getDragStyles();
      const current = toast();
      const {
        domIndex,
        visibleIndex,
        offsetY
      } = stackIndices();
      const index = current.transitionStatus === "ending" ? domIndex : visibleIndex;
      return {
        ...dragStyles,
        [TOAST_INDEX]: index,
        [TOAST_OFFSET_Y]: `${offsetY}px`,
        ...(current.height != null && current.height > 0 ? {
          [TOAST_HEIGHT]: `${current.height}px`
        } : {})
      };
    },
    get "data-starting-style"() {
      return toast().transitionStatus === "starting" ? "" : undefined;
    },
    get "data-ending-style"() {
      return toast().transitionStatus === "ending" ? "" : undefined;
    },
    get "data-swipe-direction"() {
      return swipe.swipeDirection();
    },
    get "data-expanded"() {
      return store.expanded() ? "" : undefined;
    },
    get "data-limited"() {
      return toast().limited ? "" : undefined;
    },
    get "data-swiping"() {
      return swipe.swiping() ? "" : undefined;
    },
    get "data-type"() {
      return toast().type;
    },
    get inert() {
      return toast().limited ? true : undefined;
    }
  };
  const merged = mergeRootProps(mergeRootProps(internalProps, userClassStyle), userHtmlProps);
  const Tag = dynamic(() => props.as ?? "div");
  return <Tag {...merged}>
			<ToastRootContext value={contextValue}>{props.children}</ToastRootContext>
		</Tag>;
}
//# sourceMappingURL=toast-root.jsx.map
