import { assignInlineVars } from "@vanilla-extract/dynamic";
import { noop } from "es-toolkit";
import { Activity, createContext, useContext, useEffect, useRef, useState, } from "react";
import { filter, map, merge, sample, share, tap } from "rxjs";
import { useDrag } from "../../../hook/useDrag";
import { useMatchMedia } from "../../../hook/useMatchMedia";
import { keyup$, pointerUp$ } from "../../../rx/events";
import { useEvent$ } from "../../../rx/hook";
import { cx } from "../../../utils";
import { getVarName } from "../../../utils/style";
import { Drawer } from "../../drawer";
import { DragHandel, DragHandelContext, } from "../../grid/components/dragHandle";
import * as styles from "./sidebar.css";
/**
 * React context for managing sidebar layout state.
 * Defaults to an `inlineStart` variant and `open` state.
 */
let SidebarLayoutContext = createContext({
    variant: "inlineStart",
    open: true,
    onClose: noop,
    drawerMediaQuery: "(max-width: 60rem)",
});
/**
 * A responsive sidebar layout component that supports resizable aside panels,
 * multiple layout variants (start/end, inline/block), and automatic
 * conversion to a Drawer on smaller screens.
 *
 * @remarks
 * This component coordinates the state between the {@link Aside} and {@link Content}
 * components. It handles the interactive resizing logic using pointer events and RxJS.
 *
 * @example
 * Basic usage with toggle:
 * ```tsx
 * const [isOpen, setIsOpen] = useState(true);
 * return (
 *   <SidebarLayout open={isOpen} onClose={() => setIsOpen(false)}>
 *     <Aside>Sidebar Content</Aside>
 *     <Content>
 *       <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
 *       Main Content
 *     </Content>
 *   </SidebarLayout>
 * );
 * ```
 *
 * @param props - Component properties including variant and open state.
 * @returns A grid-based layout container.
 */
export function SidebarLayout({ variant = "inlineStart", open, onClose, defaultAsideSize, drawerMediaQuery = "(max-width: 60rem)", ...props }) {
    /** Tracks if the viewport matches the drawer media query. */
    let hideAside = useMatchMedia(drawerMediaQuery);
    /** Local state for the aside size, updated after resizing finishes. */
    let [internalAsideSize, setAsideAsize] = useState(defaultAsideSize);
    /** Ref to the container to apply temporary CSS variable overrides during drag. */
    let dragTarget = useRef(null);
    let { onPointerDown, drag$ } = useDrag();
    let [onResize, resize$] = useEvent$();
    /** Ref to track the "live" size during dragging before it's committed to state. */
    let tempSize = useRef(null);
    /**
     * Effect hook to manage the resizing logic.
     * It merges drag and resize events, calculates the new size based on mouse movement,
     * updates the CSS variable for immediate feedback, and commits the final size on release.
     */
    useEffect(() => {
        let parent = dragTarget.current;
        if (!parent) {
            return;
        }
        let dragEvent$ = merge(drag$, resize$).pipe(share());
        let dragEnd$ = merge(pointerUp$, keyup$).pipe(share());
        let subscription = dragEvent$
            .pipe(map((event) => {
            let aside = event.clickTarget.parentElement?.parentElement;
            if (!aside) {
                return null;
            }
            // Calculate size based on movement.
            // MovementX for horizontal layouts, MovementY for vertical layouts.
            if (variant.startsWith("block")) {
                return `${aside.clientWidth + event.movementY}px`;
            }
            return `${aside.clientWidth + event.movementX}px`;
        }), filter((size) => size !== null), tap((size) => {
            // Apply size directly to the DOM for performance (avoiding React re-renders during drag).
            tempSize.current = size;
            parent.style.setProperty(getVarName(styles.asideSize), size);
        }), sample(dragEnd$))
            .subscribe((size) => {
            // Commit the final size to React state once the interaction ends.
            setAsideAsize(size);
            tempSize.current = null;
        });
        return () => {
            subscription.unsubscribe();
        };
    }, [drag$, resize$, variant]);
    /**
     * Determine the current aside size.
     * If closed or in drawer mode, it collapses to "0".
     * Preference order: Live drag size > committed state size > undefined (CSS default).
     */
    let asideSize = !open || hideAside ? "0" : (tempSize.current ?? internalAsideSize);
    let inlineVars = assignInlineVars({
        [styles.asideSize]: asideSize ?? null,
    });
    return (<SidebarLayoutContext.Provider value={{ variant, open, onClose, drawerMediaQuery }}>
			<DragHandelContext.Provider value={{ onPointerDown, onResize }}>
				<div {...props} ref={dragTarget} className={cx(styles.layout, styles.layoutVariants[variant], props.className)} style={{
            ...inlineVars,
            ...props.style,
        }}/>
			</DragHandelContext.Provider>
		</SidebarLayoutContext.Provider>);
}
/**
 * The sidebar (aside) panel of the {@link SidebarLayout}.
 *
 * @remarks
 * This component is polymorphic:
 * - On large screens: It renders as a standard `<aside>` with a resize handle.
 * - On small screens (defined by `drawerMediaQuery`): It renders inside a {@link Drawer}.
 *
 * It uses the {@link Activity} component to manage visibility state for standard sidebars
 * without unmounting unnecessarily.
 *
 * @example
 * ```tsx
 * <Aside drawerProps={{ placement: 'left' }}>
 *   <nav>Navigation links...</nav>
 * </Aside>
 * ```
 *
 * @param props - Component properties and children content.
 * @returns An aside element or a Drawer component.
 */
export function Aside({ drawerProps, ...props }) {
    let { open, onClose, drawerMediaQuery, variant } = useContext(SidebarLayoutContext);
    let useDrawer = useMatchMedia(drawerMediaQuery);
    if (useDrawer) {
        return (<>
				<div />
				<Drawer {...drawerProps} size={drawerProps?.size ?? styles.asideSize} open={open} onClose={onClose} variant={variant}>
					<Activity mode={open ? "visible" : "hidden"}>
						{props.children}
					</Activity>
				</Drawer>
			</>);
    }
    let isVertical = variant.startsWith("inline");
    let orientation = isVertical ? "vertical" : "horizontal";
    return (<Activity mode={open ? "visible" : "hidden"}>
			<aside {...props} className={cx(styles.aside, props.className)}>
				{props.children}
				<DragHandel index={1} orientation={orientation} className={styles.asideDragHandel.vertical}/>
			</aside>
		</Activity>);
}
/**
 * The main content area component for the {@link SidebarLayout}.
 *
 * @remarks
 * This component simply wraps a `<section>` element with the appropriate grid area
 * styles to occupy the non-sidebar space of the layout.
 *
 * @param props - Standard HTML section properties.
 * @returns A styled section element.
 */
export function Content(props) {
    return <section {...props} className={cx(styles.content, props.className)}/>;
}
