import { jsx as _jsx } from "react/jsx-runtime";
import { useState, useRef, useCallback, useMemo, useLayoutEffect, useEffect, useId, } from 'react';
import { Box, ResizeObserver } from 'ink';
import { useKeypress } from '../../hooks/useKeypress.js';
import { useScrollable } from '../../contexts/ScrollProvider.js';
import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js';
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
import { Command } from '../../key/keyMatchers.js';
import { useOverflowActions } from '../../contexts/OverflowContext.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
export const Scrollable = ({ children, width, height, maxWidth, maxHeight, hasFocus, scrollToBottom, flexGrow, reportOverflow = false, }) => {
    const keyMatchers = useKeyMatchers();
    const [scrollTop, setScrollTop] = useState(0);
    const viewportRef = useRef(null);
    const contentRef = useRef(null);
    const overflowActions = useOverflowActions();
    const id = useId();
    const [size, setSize] = useState({
        innerHeight: typeof height === 'number' ? height : 0,
        scrollHeight: 0,
    });
    const sizeRef = useRef(size);
    const scrollTopRef = useRef(scrollTop);
    useLayoutEffect(() => {
        sizeRef.current = size;
    }, [size]);
    useLayoutEffect(() => {
        scrollTopRef.current = scrollTop;
    }, [scrollTop]);
    useEffect(() => {
        if (reportOverflow && size.scrollHeight > size.innerHeight) {
            overflowActions?.addOverflowingId?.(id);
        }
        else {
            overflowActions?.removeOverflowingId?.(id);
        }
    }, [
        reportOverflow,
        size.scrollHeight,
        size.innerHeight,
        id,
        overflowActions,
    ]);
    useEffect(() => () => {
        overflowActions?.removeOverflowingId?.(id);
    }, [id, overflowActions]);
    const viewportObserverRef = useRef(null);
    const contentObserverRef = useRef(null);
    const viewportRefCallback = useCallback((node) => {
        viewportObserverRef.current?.disconnect();
        viewportRef.current = node;
        if (node) {
            const observer = new ResizeObserver((entries) => {
                const entry = entries[0];
                if (entry) {
                    const innerHeight = Math.round(entry.contentRect.height);
                    setSize((prev) => {
                        const scrollHeight = prev.scrollHeight;
                        const isAtBottom = scrollHeight > prev.innerHeight &&
                            scrollTopRef.current >= scrollHeight - prev.innerHeight - 1;
                        if (isAtBottom) {
                            setScrollTop(Number.MAX_SAFE_INTEGER);
                        }
                        return { ...prev, innerHeight };
                    });
                }
            });
            observer.observe(node);
            viewportObserverRef.current = observer;
        }
    }, []);
    const contentRefCallback = useCallback((node) => {
        contentObserverRef.current?.disconnect();
        contentRef.current = node;
        if (node) {
            const observer = new ResizeObserver((entries) => {
                const entry = entries[0];
                if (entry) {
                    const scrollHeight = Math.round(entry.contentRect.height);
                    setSize((prev) => {
                        const innerHeight = prev.innerHeight;
                        const isAtBottom = prev.scrollHeight > innerHeight &&
                            scrollTopRef.current >= prev.scrollHeight - innerHeight - 1;
                        if (isAtBottom ||
                            (scrollToBottom && scrollHeight > prev.scrollHeight)) {
                            setScrollTop(Number.MAX_SAFE_INTEGER);
                        }
                        return { ...prev, scrollHeight };
                    });
                }
            });
            observer.observe(node);
            contentObserverRef.current = observer;
        }
    }, [scrollToBottom]);
    const { getScrollTop, setPendingScrollTop } = useBatchedScroll(scrollTop);
    const scrollBy = useCallback((delta) => {
        const { scrollHeight, innerHeight } = sizeRef.current;
        const maxScroll = Math.max(0, scrollHeight - innerHeight);
        const current = Math.min(getScrollTop(), maxScroll);
        let next = Math.max(0, current + delta);
        if (next >= maxScroll) {
            next = Number.MAX_SAFE_INTEGER;
        }
        setPendingScrollTop(next);
        setScrollTop(next);
    }, [getScrollTop, setPendingScrollTop]);
    const { scrollbarColor, flashScrollbar, scrollByWithAnimation } = useAnimatedScrollbar(hasFocus, scrollBy);
    useKeypress((key) => {
        const { scrollHeight, innerHeight } = sizeRef.current;
        const scrollTop = getScrollTop();
        const maxScroll = Math.max(0, scrollHeight - innerHeight);
        const actualScrollTop = Math.min(scrollTop, maxScroll);
        // Only capture scroll-up events if there's room;
        // otherwise allow events to bubble.
        if (actualScrollTop > 0) {
            if (keyMatchers[Command.PAGE_UP](key)) {
                scrollByWithAnimation(-innerHeight);
                return true;
            }
            if (keyMatchers[Command.SCROLL_UP](key)) {
                scrollByWithAnimation(-1);
                return true;
            }
        }
        // Only capture scroll-down events if there's room;
        // otherwise allow events to bubble.
        if (actualScrollTop < maxScroll) {
            if (keyMatchers[Command.PAGE_DOWN](key)) {
                scrollByWithAnimation(innerHeight);
                return true;
            }
            if (keyMatchers[Command.SCROLL_DOWN](key)) {
                scrollByWithAnimation(1);
                return true;
            }
        }
        // bubble keypress
        return false;
    }, { isActive: hasFocus });
    const getScrollState = useCallback(() => {
        const maxScroll = Math.max(0, size.scrollHeight - size.innerHeight);
        return {
            scrollTop: Math.min(getScrollTop(), maxScroll),
            scrollHeight: size.scrollHeight,
            innerHeight: size.innerHeight,
        };
    }, [getScrollTop, size.scrollHeight, size.innerHeight]);
    const hasFocusCallback = useCallback(() => hasFocus, [hasFocus]);
    const scrollableEntry = useMemo(() => ({
        // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
        ref: viewportRef,
        getScrollState,
        scrollBy: scrollByWithAnimation,
        hasFocus: hasFocusCallback,
        flashScrollbar,
    }), [getScrollState, scrollByWithAnimation, hasFocusCallback, flashScrollbar]);
    useScrollable(scrollableEntry, true);
    return (_jsx(Box, { ref: viewportRefCallback, maxHeight: maxHeight, width: width ?? maxWidth, height: height, flexDirection: "column", overflowY: "scroll", overflowX: "hidden", scrollTop: scrollTop, flexGrow: flexGrow, scrollbarThumbColor: scrollbarColor, children: _jsx(Box, { ref: contentRefCallback, flexShrink: 0, paddingRight: 1, flexDirection: "column", children: children }) }));
};
//# sourceMappingURL=Scrollable.js.map