'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createContext, Fragment, useCallback, useContext, useEffect, useId, useRef, useSyncExternalStore, } from 'react';
import { useMergedRefs } from '@base-ui/utils/useMergedRefs';
import { objectKeys } from 'ts-extras';
import { useCSSPropsPolyfill, useFirstRender, usePrevious, useStyle } from "../hooks/index.js";
import { cn, getCSSPropsAttributes } from "../utils/index.js";
import { Presence } from "./base/Presence.js";
import { Render } from "./base/Render.js";
import { PaneContainerMeta, PaneMeta } from "./Pane.meta.js";
import { ToggleButton } from "./ToggleButton.js";
const DEFAULT_DURATION = 200;
/** Parse a CSS duration string (e.g., "200ms", "0.2s") to milliseconds. */
function parseDuration(value) {
    if (!value)
        return null;
    const match = value.trim().match(/^([\d.]+)(ms|s)?$/i);
    if (!match)
        return null;
    const num = parseFloat(match[1]);
    if (Number.isNaN(num))
        return null;
    const unit = match[2]?.toLowerCase();
    return Math.round(unit === 's' ? num * 1000 : num);
}
function createPaneStore(storeId) {
    const panes = new Map();
    // Map of pane name -> registrant ID -> callbacks
    const callbacksByRegistrant = new Map();
    const listeners = new Set();
    const notify = () => listeners.forEach((l) => l());
    return {
        subscribe: (listener) => {
            listeners.add(listener);
            return () => listeners.delete(listener);
        },
        get: (name) => panes.get(name),
        register: (name, registrantId, initial, cached) => {
            const existing = panes.get(name);
            if (existing) {
                existing.registrants.add(registrantId);
                return;
            }
            const registrants = new Set([registrantId]);
            const id = `gds-pane-${storeId}-${name}`;
            panes.set(name, cached
                ? { ...cached, registrants }
                : { ...initial, id, layout: PaneMeta.cssProps.layout.defaultValue, registrants });
            // Defer notification to avoid updating components during render
            window.queueMicrotask(notify);
        },
        unregister: (name, registrantId) => {
            const pane = panes.get(name);
            if (!pane)
                return;
            pane.registrants.delete(registrantId);
            // Remove this registrant's callbacks
            callbacksByRegistrant.get(name)?.delete(registrantId);
            if (pane.registrants.size === 0) {
                panes.delete(name);
                callbacksByRegistrant.delete(name);
            }
            // No notify - subscribers will re-register on remount if needed
        },
        setCallbacks: (name, registrantId, cbs) => {
            let paneCallbacks = callbacksByRegistrant.get(name);
            if (!paneCallbacks) {
                paneCallbacks = new Map();
                callbacksByRegistrant.set(name, paneCallbacks);
            }
            paneCallbacks.set(registrantId, cbs);
        },
        update: (name, updates) => {
            const pane = panes.get(name);
            if (!pane)
                return;
            const hasChanges = objectKeys(updates).some((key) => pane[key] !== updates[key]);
            if (!hasChanges)
                return;
            const collapsedChanged = 'collapsed' in updates && updates.collapsed !== pane.collapsed;
            const overlayOpenChanged = 'overlayOpen' in updates && updates.overlayOpen !== pane.overlayOpen;
            panes.set(name, { ...pane, ...updates });
            notify();
            // Fire all registered callbacks for this pane
            const paneCallbacks = callbacksByRegistrant.get(name);
            if (paneCallbacks) {
                paneCallbacks.forEach((cbs) => {
                    if (collapsedChanged)
                        cbs.onCollapsedChange?.(updates.collapsed);
                    if (overlayOpenChanged)
                        cbs.onOverlayOpenChange?.(updates.overlayOpen);
                });
            }
        },
    };
}
const PaneProviderContext = createContext(null);
export function PaneProvider({ children }) {
    const storeId = useId();
    const storeRef = useRef(null);
    if (!storeRef.current) {
        storeRef.current = createPaneStore(storeId);
    }
    return (_jsx(PaneProviderContext.Provider, { value: storeRef.current, children: children }));
}
PaneProvider.displayName = 'Pane.Provider';
function usePaneInternal(name, options = {}) {
    const { defaultCollapsed = false, defaultOverlayOpen = false, onCollapsedChange, onOverlayOpenChange, } = options;
    const store = useContext(PaneProviderContext);
    if (!store) {
        throw new Error('[Pane] Must be used within GDSProvider or Pane.Provider');
    }
    const registrantId = useId();
    // Cache pane data locally so we can restore it after React strict mode's simulated unmount
    const cachedPaneRef = useRef(null);
    // Register synchronously during render so sibling components have access on first render
    store.register(name, registrantId, {
        collapsed: defaultCollapsed,
        overlayOpen: defaultOverlayOpen,
        defaultCollapsed,
        defaultOverlayOpen,
    }, cachedPaneRef.current);
    // Register callbacks with the store so they fire for any state change
    store.setCallbacks(name, registrantId, { onCollapsedChange, onOverlayOpenChange });
    // Unregister on unmount
    useEffect(() => {
        return () => {
            // Save current state to cache before unregistering
            cachedPaneRef.current = store.get(name) ?? null;
            store.unregister(name, registrantId);
        };
    }, [store, name, registrantId]);
    // Subscribe to pane state changes
    const getSnapshot = useCallback(() => store.get(name), [store, name]);
    const pane = useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
    const setCollapsed = useCallback((collapsed) => store.update(name, { collapsed }), [store, name]);
    const setOverlayOpen = useCallback((overlayOpen) => store.update(name, { overlayOpen }), [store, name]);
    const toggle = useCallback(() => {
        if (pane.layout === 'overlay') {
            store.update(name, { overlayOpen: !pane.overlayOpen });
        }
        else {
            store.update(name, { collapsed: !pane.collapsed });
        }
    }, [store, name, pane.layout, pane.overlayOpen, pane.collapsed]);
    return { store, pane, setCollapsed, setOverlayOpen, toggle };
}
export function usePane(name, options = {}) {
    const { pane, setCollapsed, setOverlayOpen, toggle } = usePaneInternal(name, options);
    return {
        layout: pane.layout,
        collapsed: pane.collapsed,
        overlayOpen: pane.overlayOpen,
        setCollapsed,
        setOverlayOpen,
        toggle,
    };
}
export function PaneContainer({ ref: passedRef, orientation, className, style, children, ...props }) {
    const [cssPropsPolyfillPassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(PaneContainerMeta, { orientation }, { ref: passedRef });
    return (_jsx("div", { ref: cssPropsPolyfillPassedRef, className: cn('gds-pane-container flex u:overflow-clip', className), ...getCSSPropsAttributes(PaneContainerMeta, { orientation }, style), ...cssPropsPolyfillAttributes, ...props, children: _jsx("div", { className: `
          flex grow
          @prop-orientation-horizontal/pane-container:flex-row
          @prop-orientation-vertical/pane-container:flex-col
          u:*:isolate
          u:@prop-orientation-horizontal/pane-container:*:not-pane:w-0
          u:@prop-orientation-horizontal/pane-container:*:not-pane:grow
          u:@prop-orientation-horizontal/pane-container:*:pane:w-[clamp(--spacing(48),25%,--spacing(80))]
        `, children: children }) }));
}
PaneContainer.displayName = 'Pane.Container';
export function PaneRoot({ ref: passedRef, as: Element = 'div', name, layout, side, overlayModal = true, className, style, children, ...props }) {
    const { store, pane } = usePaneInternal(name);
    const rootRef = useRef(null);
    // Observe `--tw-duration` to allow customizing the transition duration via CSS
    const [styleCallbackRef, styleValues] = useStyle(['--tw-duration']);
    const duration = parseDuration(styleValues['--tw-duration']) ?? DEFAULT_DURATION;
    const rootPassedRef = useMergedRefs(rootRef, passedRef, styleCallbackRef);
    const [cssPropsPolyfillRootPassedRef, cssPropsPolyfillAttributes, cssProps] = useCSSPropsPolyfill(PaneMeta, { layout, side }, { ref: rootPassedRef, returnPropValues: { layout, side } });
    // Sync layout to store, resetting `overlayOpen` when switching from overlay to docked
    const previousLayoutRef = useRef(cssProps.layout);
    useEffect(() => {
        const previousLayout = previousLayoutRef.current;
        previousLayoutRef.current = cssProps.layout;
        if (previousLayout === 'overlay' && cssProps.layout === 'docked') {
            store.update(name, { layout: cssProps.layout, overlayOpen: pane.defaultOverlayOpen });
        }
        else {
            store.update(name, { layout: cssProps.layout });
        }
    }, [store, name, cssProps.layout, pane.defaultOverlayOpen]);
    // Pseudo-modal overlay: focus pane on open, Escape to close, restore focus on close
    const focusLeftPaneRef = useRef(false);
    const previouslyFocusedRef = useRef(null);
    useEffect(() => {
        if (!overlayModal || cssProps.layout !== 'overlay' || !pane.overlayOpen)
            return;
        focusLeftPaneRef.current = false;
        // Save the currently focused element and focus the pane content
        setTimeout(() => {
            previouslyFocusedRef.current = document.activeElement;
            rootRef.current
                ?.querySelector('[data-key="content"]')
                ?.focus({ preventScroll: true });
        }, 0);
        // Close overlay when focus moves to an element covered by the backdrop (i.e. inside the container, but outside the pane)
        const handlePaneFocusOut = (event) => {
            const newTarget = event.relatedTarget;
            // Focus intentionally left if it went somewhere other than body (which is where `inert` sends it)
            if (newTarget && newTarget !== document.body && !rootRef.current?.contains(newTarget)) {
                focusLeftPaneRef.current = true;
                if (parent?.contains(newTarget)) {
                    store.update(name, { overlayOpen: false });
                }
            }
        };
        const handlePaneFocusIn = () => {
            focusLeftPaneRef.current = false;
        };
        // Also listen on parent to catch focus entering the backdrop area from outside
        const handleParentFocusIn = (event) => {
            if (!rootRef.current?.contains(event.target)) {
                focusLeftPaneRef.current = true;
                store.update(name, { overlayOpen: false });
            }
        };
        const root = rootRef.current;
        const parent = root?.parentElement;
        root?.addEventListener('focusout', handlePaneFocusOut);
        root?.addEventListener('focusin', handlePaneFocusIn);
        parent?.addEventListener('focusin', handleParentFocusIn);
        // Close on Escape when focus is inside the pane
        const handleKeyDown = (event) => {
            if (event.key === 'Escape' && rootRef.current?.contains(document.activeElement)) {
                store.update(name, { overlayOpen: false });
            }
        };
        document.addEventListener('keydown', handleKeyDown);
        // Cleanup: remove handlers and restore focus unless it intentionally left the pane
        return () => {
            root?.removeEventListener('focusout', handlePaneFocusOut);
            root?.removeEventListener('focusin', handlePaneFocusIn);
            parent?.removeEventListener('focusin', handleParentFocusIn);
            document.removeEventListener('keydown', handleKeyDown);
            if (!focusLeftPaneRef.current && previouslyFocusedRef.current instanceof HTMLElement) {
                previouslyFocusedRef.current.focus();
            }
            previouslyFocusedRef.current = null;
        };
    }, [store, name, overlayModal, cssProps.layout, pane.overlayOpen]);
    const open = cssProps.layout === 'overlay' ? pane.overlayOpen : !pane.collapsed;
    const presenceKeys = ['content', 'placeholder'];
    let visibleKey = open ? 'content' : 'placeholder';
    const beforeFirstTransition = useRef(true);
    const skipTransition = useRef(false);
    // Always show the content on the first render, and skip the transition if it's hidden on the next render
    const firstRender = useFirstRender();
    if (firstRender && visibleKey === 'placeholder') {
        visibleKey = 'content';
        skipTransition.current = true;
    }
    // Do not transition if the open state changed due to a layout change
    const previousVisibleKey = usePrevious(visibleKey);
    const previousLayout = usePrevious(cssProps.layout);
    if (previousVisibleKey.hasChanged && previousLayout.hasChanged) {
        skipTransition.current = true;
    }
    const setSize = (size) => {
        if (!rootRef.current)
            return;
        if (size === null) {
            rootRef.current.style.removeProperty('--pane-size');
        }
        else {
            rootRef.current.style.setProperty('--pane-size', `${size}px`);
        }
    };
    return (_jsxs(Element, { ref: cssPropsPolyfillRootPassedRef, id: pane.id, "data-gds-exposed-open": open, className: cn(`gds-pane root-contents u:z-10 u:max-w-full u:[--tw-duration:200ms]
        u:default-side-start
        u:last:default-side-end
        u:@prop-orientation-vertical/pane-container:default-side-top
        u:@prop-orientation-vertical/pane-container:last:default-side-bottom`, className), ...getCSSPropsAttributes(PaneMeta, { layout, side }, style), ...cssPropsPolyfillAttributes, ...props, children: [overlayModal ? (_jsx("div", { "aria-hidden": true, "data-open": pane.overlayOpen || undefined, onClick: () => store.update(name, { overlayOpen: false }), className: `
            absolute inset-0 z-[inherit] bg-backdrop transition [--tw-duration:inherit]
            not-data-open:pointer-events-none
            not-data-open:opacity-0
            @prop-layout-docked/pane:hidden
          ` })) : null, _jsx(Presence, { visibleKey: visibleKey, enterMs: skipTransition.current ? 0 : duration, exitMs: skipTransition.current ? 0 : duration, onBeforeTransitionStart: ({ exiting, previouslyTransitioning }) => {
                    beforeFirstTransition.current = false;
                    if (skipTransition.current ||
                        cssProps.layout === 'overlay' ||
                        previouslyTransitioning.size > 0) {
                        return;
                    }
                    const exitingContent = exiting.get('content');
                    if (!exitingContent)
                        return;
                    setSize(cssProps.side === 'start' || cssProps.side === 'end'
                        ? exitingContent.offsetWidth
                        : exitingContent.offsetHeight);
                }, onTransitionStart: ({ entering, previouslyTransitioning }) => {
                    if (skipTransition.current ||
                        cssProps.layout === 'overlay' ||
                        previouslyTransitioning.size > 0) {
                        return;
                    }
                    const enteringContent = entering.get('content');
                    if (!enteringContent)
                        return;
                    enteringContent.style.setProperty('width', 'inherit');
                    enteringContent.style.setProperty('height', 'inherit');
                    enteringContent.style.setProperty('position', 'relative');
                    setSize(cssProps.side === 'start' || cssProps.side === 'end'
                        ? enteringContent.offsetWidth
                        : enteringContent.offsetHeight);
                    enteringContent.style.removeProperty('width');
                    enteringContent.style.removeProperty('height');
                    enteringContent.style.removeProperty('position');
                }, onTransitionEnd: () => {
                    skipTransition.current = false;
                }, renderChild: (renderProps, state) => {
                    const hiddenStyle = state.status === 'exiting' ||
                        state.status === 'hidden' ||
                        (state.status === 'entering' && state.previousStatus !== null);
                    switch (state.key) {
                        case 'content': {
                            return (_jsx("div", { ...renderProps, tabIndex: overlayModal && cssProps.layout === 'overlay' ? -1 : undefined, "data-entering-or-exiting": state.status === 'entering' || state.status === 'exiting' || undefined, "data-hidden-style": hiddenStyle || undefined, "data-hidden-if-overlay": (beforeFirstTransition.current && !pane.overlayOpen) || undefined, "data-layout": cssProps.layout, "data-side": cssProps.side, className: cn(renderProps.className, `inherit-position inherit-layout inherit-paint transition-transform [--tw-duration:inherit]
                    @prop-side-top/pane:inset-x-0
                    @prop-side-top/pane:top-0
                    @prop-side-top/pane:data-hidden-style:-translate-y-full
                    @prop-side-bottom/pane:inset-x-0
                    @prop-side-bottom/pane:bottom-0
                    @prop-side-bottom/pane:data-hidden-style:translate-y-full
                    @prop-side-start/pane:inset-y-0
                    @prop-side-start/pane:start-0
                    @prop-side-start/pane:data-hidden-style:translate-x-[calc(-100%*var(--gds-rtl-factor))]
                    @prop-side-end/pane:inset-y-0
                    @prop-side-end/pane:end-0
                    @prop-side-end/pane:data-hidden-style:translate-x-[calc(100%*var(--gds-rtl-factor))]
                    @prop-layout-docked/pane:data-entering-or-exiting:absolute
                    @prop-layout-docked/pane:data-entering-or-exiting:@prop-side-top/pane:h-(--pane-size)
                    @prop-layout-docked/pane:data-entering-or-exiting:@prop-side-bottom/pane:h-(--pane-size)
                    @prop-layout-docked/pane:data-entering-or-exiting:@prop-side-start/pane:w-(--pane-size)
                    @prop-layout-docked/pane:data-entering-or-exiting:@prop-side-end/pane:w-(--pane-size)
                    @prop-layout-overlay/pane:absolute
                    @prop-layout-overlay/pane:data-hidden-if-overlay:invisible`, 
                                /**
                                 * Same as the above but with the `@prop` variants replaced with `data-*` (which
                                 * isn't as good because the first render might be incorrect) to work around a
                                 * Safari issue where style queries are buggy inside `display: contents` (see
                                 * https://bugs.webkit.org/show_bug.cgi?id=301871).
                                 */
                                `safari:data-[layout=docked]:data-entering-or-exiting:absolute
                    safari:data-[layout=overlay]:absolute
                    safari:data-[layout=overlay]:data-hidden-if-overlay:invisible
                    safari:data-[side=bottom]:inset-x-0
                    safari:data-[side=bottom]:bottom-0
                    safari:data-[side=bottom]:data-hidden-style:translate-y-full
                    safari:data-[layout=docked]:data-entering-or-exiting:data-[side=bottom]:h-(--pane-size)
                    safari:data-[side=end]:inset-y-0
                    safari:data-[side=end]:end-0
                    safari:data-[side=end]:data-hidden-style:translate-x-[calc(100%*var(--gds-rtl-factor))]
                    safari:data-[layout=docked]:data-entering-or-exiting:data-[side=end]:w-(--pane-size)
                    safari:data-[side=start]:inset-y-0
                    safari:data-[side=start]:start-0
                    safari:data-[side=start]:data-hidden-style:translate-x-[calc(-100%*var(--gds-rtl-factor))]
                    safari:data-[layout=docked]:data-entering-or-exiting:data-[side=start]:w-(--pane-size)
                    safari:data-[side=top]:inset-x-0
                    safari:data-[side=top]:top-0
                    safari:data-[side=top]:data-hidden-style:-translate-y-full
                    safari:data-[layout=docked]:data-entering-or-exiting:data-[side=top]:h-(--pane-size)`), children: children }));
                        }
                        case 'placeholder': {
                            return (_jsx("div", { ...renderProps, hidden: undefined, "data-hidden": renderProps.hidden || undefined, "data-layout": cssProps.layout, "data-side": cssProps.side, className: cn(renderProps.className, `inherit-position shrink-0 transition-[width,height] [--size:0px] [--tw-duration:inherit]
                    data-hidden:hidden
                    data-[status=entering]:data-starting-style:[--size:var(--pane-size)]
                    data-[status=exiting]:[--size:var(--pane-size)]
                    data-[status=exiting]:data-starting-style:[--size:0px]
                    @prop-side-top/pane:h-(--size)
                    @prop-side-bottom/pane:h-(--size)
                    @prop-side-start/pane:w-(--size)
                    @prop-side-end/pane:w-(--size)`, 
                                /**
                                 * Since the content is always absolutely-positioned in overlay mode, we render
                                 * the placeholder in flow (even when it's supposed to be hidden) for the amount
                                 * of in-flow elements to remain stable across `layout` or `overlayOpen` changes
                                 * (e.g. so that sibling elements don't suddenly get auto-placed in a different
                                 * grid cell if the pane is in a grid).
                                 */
                                `i:@prop-layout-overlay/pane:block
                    i:@prop-layout-overlay/pane:size-0`, 
                                // Workaround for the Safari bug mentioned above
                                `safari:data-[side=bottom]:h-(--size)
                    safari:data-[side=end]:w-(--size)
                    safari:data-[side=start]:w-(--size)
                    safari:data-[side=top]:h-(--size)
                    i:safari:data-[layout=overlay]:block
                    i:safari:data-[layout=overlay]:size-0`) }));
                        }
                    }
                }, children: presenceKeys.map((key) => (_jsx(Fragment, {}, key))) })] }));
}
PaneRoot.displayName = 'Pane';
export function PaneToggleButton({ name, render, variant, size, hideLabel, tooltip, addonBefore, addonAfter, onClick, className, children, ...props }) {
    const { pane, toggle } = usePaneInternal(name);
    const open = pane.layout === 'overlay' ? pane.overlayOpen : !pane.collapsed;
    return (_jsx(Render, { render: render ?? (_jsx(ToggleButton, { "aria-pressed": undefined, variant: variant, size: size, hideLabel: hideLabel, addonBefore: addonBefore, addonAfter: addonAfter, tooltip: tooltip, className: cn('u:open:state-checked', className) })), state: { open }, type: "button", "aria-expanded": open, "aria-controls": pane.id, onClick: (event) => {
            toggle();
            onClick?.(event);
        }, className: render ? className : undefined, ...props, children: children }));
}
PaneToggleButton.displayName = 'Pane.ToggleButton';
//# sourceMappingURL=Pane.parts.js.map