import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { nullTranslator } from '@jupyterlab/translation';
import { JSONExt } from '@lumino/coreutils';
import { Autocomplete, FormControl, TextInputWithTokens } from '@primer/react';
import { Box } from '@datalayer/primer-addons';
/**
 * Runtime Cell Variables Picker.
 */
export function RuntimeCellVariables(props) {
    const { getInputOptions, getOutputOptions, inputs, output, setInputs, setOutput, translator, } = props;
    const trans = useMemo(() => (translator ?? nullTranslator).load('jupyterlab'), [translator]);
    const inputForOutputRef = useRef(null);
    const [willSaveOutput, setWillSaveOutput] = useState(false);
    const [inputLoading, setInputLoading] = useState(!!getInputOptions);
    const [outputsState, setOutputsState] = useState(null);
    const [inputsState, setInputsState] = useState(inputs.map(id => ({ id, text: id, selected: true })));
    const [filterVal, setFilterVal] = useState('');
    // Add inputs prop in inputsState
    useEffect(() => {
        const candidates = inputs.filter(item => !inputsState.find(localItem => localItem.text === item));
        if (candidates.length) {
            setInputsState([
                ...inputsState,
                ...candidates.map(text => ({ id: text, text, selected: true })),
            ]);
        }
    }, [inputs]);
    // Update inputs with selected inputsState
    useEffect(() => {
        const candidates = inputsState
            .filter(item => item.selected)
            .map(item => item.text);
        if (!JSONExt.deepEqual(inputs, candidates)) {
            setInputs(candidates);
        }
    }, [inputsState]);
    // Fetch the input candidates
    useEffect(() => {
        if (!inputLoading) {
            return;
        }
        if (getInputOptions) {
            getInputOptions().then(ins => {
                const oldItems = inputsState.map(i => i.text);
                setInputsState([
                    ...inputsState,
                    ...ins
                        .sort()
                        .filter(item => !oldItems.includes(item))
                        .map(text => ({
                        id: text,
                        text,
                        selected: false,
                    })),
                ]);
                setInputLoading(false);
            });
        }
        else {
            setInputLoading(false);
        }
    }, [inputsState, getInputOptions]);
    // Fetch the output candidates
    useEffect(() => {
        if (getOutputOptions) {
            Promise.all([getOutputOptions(), getInputOptions?.()])
                .then(([outs, ins]) => {
                setOutputsState(
                // Add input variables as output candidate to cover
                // mutation case
                outs
                    .sort()
                    .concat((ins ?? []).sort())
                    // Remove duplicated variables; it may happen if
                    // the code introspection matches an input.
                    .reduce((agg, value) => {
                    if (!agg.includes(value)) {
                        agg.push(value);
                    }
                    return agg;
                }, [])
                    .map(text => ({
                    id: text,
                    text,
                    selected: false,
                })));
            })
                .catch(err => {
                console.error('Failed to get the cell output candidates', err);
                setOutputsState([]);
            });
        }
    }, [getOutputOptions, getInputOptions]);
    const onTokenRemove = useCallback((tokenId) => {
        const idx = inputsState.findIndex(item => item.id === tokenId);
        if (idx >= 0) {
            inputsState.splice(idx, 1);
            setInputsState([...inputsState]);
        }
    }, [inputsState]);
    const onSelectedInputsChange = useCallback((newlySelectedItems) => {
        if (!Array.isArray(newlySelectedItems)) {
            newlySelectedItems = [newlySelectedItems];
        }
        const selectedIds = newlySelectedItems.map(i => i.id);
        setInputsState([
            ...inputsState.map(localItem => selectedIds.includes(localItem.id)
                ? Object.assign(localItem, { selected: true })
                : Object.assign(localItem, { selected: false })),
        ]);
    }, [inputsState]);
    const onItemSelect = useCallback((item) => {
        const oldItem = inputsState.find(localItem => localItem.id === item.id);
        if (!oldItem) {
            setInputsState([...inputsState, item]);
        }
        else {
            oldItem.selected = true;
            setInputsState([...inputsState]);
        }
    }, [inputsState]);
    const handleChange = useCallback((e) => {
        if (e.currentTarget) {
            setFilterVal(e.currentTarget.value);
        }
    }, []);
    const onOutputChange = useCallback((e) => {
        if (e.currentTarget) {
            setOutput(e.currentTarget.value);
        }
    }, []);
    const handleOutputMenuOpenChange = useCallback(
    // There is a bug in primer Autocomplete that lead to the suggestion
    // not triggering the `onChange` callback. So to fix it here, we
    // use a pointer to the underlying input element to get the value
    // and set it when the autocompletion menu closes.
    (open) => {
        if (!willSaveOutput) {
            if (open) {
                setWillSaveOutput(true);
            }
            return;
        }
        if (inputForOutputRef.current && !open) {
            setOutput(inputForOutputRef.current.value);
            setWillSaveOutput(false);
        }
    }, [willSaveOutput]);
    return (_jsxs(Box, { as: "form", sx: { p: 3 }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { id: "cell-input-variables", children: trans.__('Inputs') }), _jsxs(Autocomplete, { children: [_jsx(Autocomplete.Input, { as: TextInputWithTokens, tokens: inputsState.filter(item => item.selected), onTokenRemove: onTokenRemove, onChange: handleChange }), _jsx(Autocomplete.Overlay, { children: _jsx(Autocomplete.Menu, { addNewItem: filterVal &&
                                        !inputsState.find(localItem => localItem.text === filterVal)
                                        ? {
                                            id: filterVal,
                                            text: trans.__("Add '%1'", filterVal),
                                            handleAddItem: item => {
                                                onItemSelect({
                                                    ...item,
                                                    text: filterVal,
                                                    selected: true,
                                                });
                                                setFilterVal('');
                                            },
                                        }
                                        : undefined, items: inputsState, selectedItemIds: inputsState
                                        .filter(item => item.selected)
                                        .map(item => item.id), onSelectedChange: onSelectedInputsChange, "aria-labelledby": "cell-input-variables", selectionVariant: "multiple", emptyStateText: trans.__('No available variables.'), loading: inputLoading }) })] })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { id: "cell-output-variables", children: trans.__('Output') }), _jsxs(Autocomplete, { children: [_jsx(Autocomplete.Input, { ref: inputForOutputRef, value: output, onChange: onOutputChange }), _jsx(Autocomplete.Overlay, { children: _jsx(Autocomplete.Menu, { items: outputsState ?? [], selectedItemIds: output ? [output] : [], "aria-labelledby": "cell-output-variables", loading: getOutputOptions && !outputsState, emptyStateText: trans.__('Not among the available variables.'), onOpenChange: handleOutputMenuOpenChange }) })] })] })] }));
}
export default RuntimeCellVariables;
