import { createElement, useEffect, useMemo, useRef, useState, } from "react";
import { useCombinedRefs } from "../../../hook/useCombinedRefs";
import { useInputValidation, } from "../../../hook/useInputValidation";
import { useEnterCallback } from "../../../hook/useKeyCallback";
import { useOnEscape } from "../../../hook/useOnEscape";
import { useOutsideClick } from "../../../hook/useOutsideClick";
import { cx } from "../../../utils";
import { setNativeValue, simulateFocusCycle } from "../../../utils/form";
import { button } from "../../button";
import { caption } from "../../typography";
import * as styles from "./chip-input.css";
let X = () => {
    return <span>X</span>;
};
let DefaultRender = ({ value, isFocus }) => (<li className={isFocus ? "focus" : ""}>{value}</li>);
/**
 * A component that allows users to enter multiple values as "chips".
 *
 * @remarks
 * This component manages a list of strings, rendering them as chips. It uses a hidden input
 * to store the joined values (separated by `separator`) for form submission.
 * Users can add chips by typing and pressing Enter, or by pasting text containing the separator.
 * Chips can be deleted by backspacing or clicking a delete button (if provided in `render`).
 *
 * @param props - The component props.
 * @returns The rendered chips input component.
 *
 * @example
 * ```tsx
 * import { string, array, minLength } from 'valibot';
 * import { ChipsInput, Chip, RemoveChipButton } from './chip_input';
 *
 * function TagsInput() {
 *   return (
 *     <ChipsInput
 *       name="tags"
 *       separator=","
 *       render={({ value, isFocus, onDelete }) => (
 *         <Chip className={isFocus ? 'focused' : ''}>
 *           {value}
 *           <RemoveChipButton onClick={onDelete} />
 *         </Chip>
 *       )}
 *     />
 *   );
 * }
 * ```
 */
export function ChipsInput({ separator = ",", onChange, className = "", value, defaultValue = "", render = DefaultRender, name, id, validate, onValidationError, required, onBlur, input: { validate: validateInput, onValidationError: onInputValidationError, ...inputProps } = {}, ...props }) {
    let getValues = useMemo(() => makeGetValues(separator), [separator]);
    let [state, setState] = useState(() => getValues(defaultValue));
    let currentValues = value !== undefined ? getValues(value) : state;
    let [inputFocus, setInputFocus] = useState(false);
    let input = useRef(null);
    let [focus, setFocus] = useState(null);
    let onCancelDelete = () => setFocus(null);
    let inputRef = useRef(null);
    let setChange = (values) => {
        if (inputRef.current) {
            let value = values.join(separator);
            setNativeValue(inputRef.current, value);
            inputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
            setState(values);
        }
    };
    let handleSet = setChange;
    let wrapper = useRef(null);
    useEffect(() => {
        if (!wrapper.current) {
            return;
        }
        let form = wrapper.current.closest("form");
        if (!form) {
            return;
        }
        let handleReset = () => {
            requestAnimationFrame(() => {
                setState(getValues(defaultValue));
            });
        };
        form.addEventListener("reset", handleReset);
        return () => {
            form?.removeEventListener("reset", handleReset);
        };
    }, [getValues, defaultValue]);
    function handleClick(e) {
        if (!input.current) {
            return;
        }
        let isTarget = e.target === wrapper.current;
        if (isTarget) {
            input.current.focus();
        }
    }
    let onKeyDown = useEnterCallback((e) => {
        if (!input.current) {
            return;
        }
        let isTarget = e.target === wrapper.current;
        if (isTarget) {
            input.current.focus();
        }
    });
    function handleAdd(chips) {
        let values = new Set(value ? [...getValues(value), ...chips] : [...state, ...chips]);
        handleSet([...values]);
    }
    function handleInputChange(e) {
        if (inputProps.onChange) {
            inputProps.onChange(e);
        }
        if (e.isDefaultPrevented()) {
            return;
        }
        let { value: newValue } = e.target;
        onCancelDelete();
        if (newValue.includes(separator)) {
            let chips = newValue
                .split(separator)
                .map((str) => str.trim())
                .filter(Boolean);
            handleAdd(chips);
            e.target.value = "";
        }
    }
    function deleteAt(index) {
        let newValues = [
            ...currentValues.slice(0, index),
            ...currentValues.slice(index + 1),
        ];
        handleSet(newValues);
        if (focus === index) {
            setFocus(null);
        }
    }
    function handleDelete() {
        if (focus !== null) {
            deleteAt(currentValues.length - 1);
            setFocus(null);
        }
        else {
            setFocus(currentValues.length - 1);
        }
    }
    function handleCancel() {
        if (inputFocus) {
            onCancelDelete();
        }
    }
    useOnEscape(handleCancel);
    let outsideClick = useOutsideClick(handleCancel);
    let listRef = useCombinedRefs(wrapper, outsideClick);
    let hiddenInputRef = useCombinedRefs(inputRef);
    let handleInputFocus = (e) => {
        if (inputProps.onFocus) {
            inputProps.onFocus(e);
        }
        if (e.isDefaultPrevented()) {
            return;
        }
        setInputFocus(true);
        setFocus(null);
    };
    let handleInputBlur = (e) => {
        if (inputProps.onBlur) {
            inputProps.onBlur(e);
        }
        if (e.isDefaultPrevented()) {
            return;
        }
        setInputFocus(false);
        simulateFocusCycle(inputRef.current);
    };
    let validationHandlers = useInputValidation({
        name,
        validate,
        required,
        onValidationError,
        onChange,
        onBlur,
        reportValidityTarget: input,
    });
    let validateInputHandlers = useInputValidation({
        validate: validateInput,
        required: inputProps.required,
        onValidationError: onInputValidationError,
    });
    function handleInputKeyDown(e) {
        if (inputProps.onKeyDown) {
            inputProps.onKeyDown(e);
        }
        if (e.isDefaultPrevented()) {
            return;
        }
        e.currentTarget.setCustomValidity("");
        if (e.code === "Backspace") {
            let target = e.target;
            let isEmpty = target.value === "";
            if (isEmpty) {
                handleDelete();
            }
        }
        else if (e.code === "Enter" && input.current) {
            e.preventDefault();
            e.stopPropagation();
            let issues = validateInputHandlers.validate(input.current);
            if (issues) {
                return;
            }
            let currentValue = input.current.value;
            let newValue = currentValue.trim();
            if (newValue === "") {
                return;
            }
            handleAdd([newValue]);
            input.current.value = "";
        }
    }
    let handleInputClick = (event) => {
        if (inputProps.onClick) {
            inputProps.onClick(event);
        }
        if (event.isDefaultPrevented()) {
            return;
        }
        onCancelDelete();
    };
    return (<div {...props} className={cx(className, styles.wrapper)}>
			<input aria-hidden style={{
            position: "absolute",
            insetBlockEnd: 0,
            insetInlineEnd: 0,
            opacity: "0",
            pointerEvents: "none",
        }} ref={hiddenInputRef} name={name} value={currentValues.join(separator)} onChange={validationHandlers.onChange} onBlur={validationHandlers.onBlur} required={required} onInvalid={validationHandlers.onInvalid}/>

			<ul ref={listRef} onClick={handleClick} onKeyDown={onKeyDown} className={styles.chips}>
				{currentValues.map((value, index) => {
            let isFocus = index === focus;
            let onDelete = () => {
                deleteAt(index);
            };
            let key = value;
            return createElement(render, { key, value, isFocus, onDelete });
        })}

				<li className={styles.inputWrapper}>
					<input className={styles.input} ref={input} type="text" id={id} {...inputProps} onFocus={handleInputFocus} onBlur={handleInputBlur} onChange={handleInputChange} onKeyDown={handleInputKeyDown} onClick={handleInputClick}/>
				</li>
			</ul>
		</div>);
}
/**
 * A button component for removing a chip.
 *
 * @param props - Standard HTML button attributes.
 * @returns The rendered button element.
 *
 * @example
 * ```tsx
 * <RemoveChipButton onClick={handleRemove} />
 * ```
 */
export let RemoveChipButton = ({ className, ...props }) => {
    return (<button {...props} type="button" className={cx(className, styles.removeChipButton)}>
			<X />
		</button>);
};
/**
 * Creates a function to parse a separated string into an array of values.
 *
 * @param separator - The separator character.
 * @returns A function that takes a string and returns an array of trimmed, non-empty strings.
 */
export function makeGetValues(separator) {
    return (value = "") => value
        .split(separator)
        .map((str) => str.trim())
        .filter(Boolean);
}
/**
 * A styled chip component.
 *
 * @param props - The component props.
 * @returns The rendered chip element.
 *
 * @example
 * ```tsx
 * <Chip>React</Chip>
 * ```
 */
export function Chip({ children, size = "size", ...props }) {
    return (<li {...props} className={cx(button, caption[size], props.className)}>
			{children}
		</li>);
}
