'use client';

/**
 * SidePanel — a non-modal side drawer.
 *
 * Use for inspector panels, playgrounds, filters — anywhere you want a
 * slide-in surface that does NOT lock out the rest of the page. The
 * surrounding UI stays clickable and focusable; only Esc (optional) and
 * the close button dismiss the panel.
 *
 * Differences from the existing ``Drawer``:
 *   - ``modal={false}``  — no focus trap, no scroll-lock on <body>.
 *   - No backdrop overlay.
 *   - No ``shouldScaleBackground`` (vaul's iOS-style fancy scale looks
 *     wrong for side panels — reserved for bottom sheets).
 *   - Opinionated for ``right``/``left`` directions; vaul still drives
 *     the transform + swipe-to-close gesture underneath.
 *
 * Layout is intentionally similar to our Sheet/Drawer components so
 * callers feel at home.
 */

import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { X } from 'lucide-react';

import { cn } from '../../../lib/utils';

// ─── Root ─────────────────────────────────────────────────────────────────────

export interface SidePanelProps {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    children: React.ReactNode;
    /** ``'right'`` (default) slides in from the right edge; ``'left'`` from the left. */
    side?: 'right' | 'left';
    /** Close when the user presses Escape. Default ``true``. Disable when
     *  the parent wants custom handling or Esc is bound to something else. */
    closeOnEsc?: boolean;
}

const SidePanel: React.FC<SidePanelProps> & {
    Content: typeof SidePanelContent;
    Header: typeof SidePanelHeader;
    Title: typeof SidePanelTitle;
    Description: typeof SidePanelDescription;
    Body: typeof SidePanelBody;
    Footer: typeof SidePanelFooter;
    Close: typeof SidePanelClose;
} = ({ open, onOpenChange, children, side = 'right', closeOnEsc = true }) => {
    // Esc handling: vaul's built-in closes on Esc only when modal=true.
    // We're non-modal, so we install our own listener — gated on ``open``
    // to avoid swallowing Esc globally while the panel is closed.
    React.useEffect(() => {
        if (!open || !closeOnEsc) return;
        const handler = (e: KeyboardEvent) => {
            if (e.key === 'Escape') onOpenChange(false);
        };
        window.addEventListener('keydown', handler);
        return () => window.removeEventListener('keydown', handler);
    }, [open, closeOnEsc, onOpenChange]);

    return (
        <DrawerPrimitive.Root
            open={open}
            onOpenChange={onOpenChange}
            direction={side}
            modal={false}
            shouldScaleBackground={false}
            // vaul defaults to mutating ``<body>`` styles (position:fixed,
            // overflow:hidden, pointer-events:none) to support swipe-to-
            // close. For a non-modal side panel that behaviour is wrong —
            // it disables every interaction outside the panel including
            // page scroll. ``noBodyStyles`` tells vaul to keep its hands
            // off the document.
            noBodyStyles
            disablePreventScroll
            dismissible
        >
            <SidePanelSideContext.Provider value={side}>
                {children}
            </SidePanelSideContext.Provider>
        </DrawerPrimitive.Root>
    );
};
SidePanel.displayName = 'SidePanel';

// Carries the side down to Content so it can place border + transform correctly.
const SidePanelSideContext = React.createContext<'right' | 'left'>('right');

// ─── Content ──────────────────────────────────────────────────────────────────

export interface SidePanelContentProps extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {
    /** CSS width (any valid value: ``'440px'``, ``'32rem'``, ``'min(440px, 90vw)'``).
     *  Default ``'440px'``. Override via ``className`` with ``w-*`` if you
     *  prefer Tailwind. */
    width?: string;
}

const SidePanelContent = React.forwardRef<
    React.ElementRef<typeof DrawerPrimitive.Content>,
    SidePanelContentProps
>(({ className, children, width = '440px', style, ...props }, ref) => {
    const side = React.useContext(SidePanelSideContext);
    const positioning =
        side === 'right' ? 'inset-y-0 right-0 border-l' : 'inset-y-0 left-0 border-r';

    return (
        <DrawerPrimitive.Portal>
            <DrawerPrimitive.Content
                ref={ref}
                aria-describedby={undefined}
                className={cn(
                    'fixed z-500 flex flex-col bg-background shadow-2xl shadow-black/20',
                    'h-full max-w-[95vw]',
                    positioning,
                    className,
                )}
                style={{
                    width,
                    // Animate both the slide-in (transform, driven by vaul)
                    // and width changes (when callers swap width to reveal
                    // secondary content). Separate transition strings so
                    // the two can have different curves if needed.
                    transition:
                        'transform 300ms cubic-bezier(0.32, 0.72, 0, 1),' +
                        ' width 250ms cubic-bezier(0.32, 0.72, 0, 1)',
                    ...style,
                }}
                {...props}
            >
                {children}
            </DrawerPrimitive.Content>
        </DrawerPrimitive.Portal>
    );
});
SidePanelContent.displayName = 'SidePanelContent';

// ─── Header / Title / Description ─────────────────────────────────────────────

const SidePanelHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
    ({ className, ...props }, ref) => (
        <div
            ref={ref}
            className={cn(
                'shrink-0 flex items-center gap-3 px-4 h-12 border-b bg-muted/20',
                className,
            )}
            {...props}
        />
    ),
);
SidePanelHeader.displayName = 'SidePanelHeader';

const SidePanelTitle = React.forwardRef<
    React.ElementRef<typeof DrawerPrimitive.Title>,
    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
    <DrawerPrimitive.Title
        ref={ref}
        className={cn(
            'text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/60',
            className,
        )}
        {...props}
    />
));
SidePanelTitle.displayName = 'SidePanelTitle';

const SidePanelDescription = React.forwardRef<
    React.ElementRef<typeof DrawerPrimitive.Description>,
    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
    <DrawerPrimitive.Description
        ref={ref}
        className={cn('text-xs text-muted-foreground', className)}
        {...props}
    />
));
SidePanelDescription.displayName = 'SidePanelDescription';

// ─── Body / Footer ────────────────────────────────────────────────────────────

const SidePanelBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
    ({ className, ...props }, ref) => (
        <div
            ref={ref}
            className={cn('flex-1 min-h-0 overflow-y-auto', className)}
            {...props}
        />
    ),
);
SidePanelBody.displayName = 'SidePanelBody';

const SidePanelFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
    ({ className, ...props }, ref) => (
        <div
            ref={ref}
            className={cn('shrink-0 border-t px-4 py-3 bg-background/95', className)}
            {...props}
        />
    ),
);
SidePanelFooter.displayName = 'SidePanelFooter';

// ─── Close ────────────────────────────────────────────────────────────────────

export interface SidePanelCloseProps
    extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'children'> {
    /** Accessible label. Default ``"Close panel"``. */
    label?: string;
    /** Replace the default ``X`` icon with custom content. */
    children?: React.ReactNode;
}

const SidePanelClose = React.forwardRef<HTMLButtonElement, SidePanelCloseProps>(
    ({ className, label = 'Close panel', children, ...props }, ref) => (
        <DrawerPrimitive.Close asChild>
            <button
                ref={ref}
                type="button"
                aria-label={label}
                title={`${label} (Esc)`}
                className={cn(
                    'shrink-0 h-7 w-7 inline-flex items-center justify-center rounded-[var(--radius-sm)]',
                    'text-muted-foreground hover:text-foreground hover:bg-muted transition-colors',
                    className,
                )}
                {...props}
            >
                {children ?? <X className="h-4 w-4" />}
            </button>
        </DrawerPrimitive.Close>
    ),
);
SidePanelClose.displayName = 'SidePanelClose';

// Attach subcomponents to the root for ergonomic dot-access.
SidePanel.Content = SidePanelContent;
SidePanel.Header = SidePanelHeader;
SidePanel.Title = SidePanelTitle;
SidePanel.Description = SidePanelDescription;
SidePanel.Body = SidePanelBody;
SidePanel.Footer = SidePanelFooter;
SidePanel.Close = SidePanelClose;

export {
    SidePanel,
    SidePanelContent,
    SidePanelHeader,
    SidePanelTitle,
    SidePanelDescription,
    SidePanelBody,
    SidePanelFooter,
    SidePanelClose,
};
