import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useCallback, useState, useEffect, useRef, useLayoutEffect } from 'react';
import FormField from '../FormField';
import { StyledFormControl } from '../FormControl';
import { hasProp, withTestIds } from '../../utils';
import { useConsolidatedRef, useElement, useI18n, useLiveLog, useTestIds, useUID } from '../../hooks';
import Text from '../Text';
import VisuallyHiddenText from '../VisuallyHiddenText';
import StyledTextArea, { textAreaMaxHeight } from './TextArea.styles';
import { getTextAreaTestIds } from './TextArea.test-ids';
const warnCharCount = 30;
const TextArea = forwardRef(function TextArea(props, ref) {
    const uid = useUID();
    const { testId, additionalInfo, id = uid, value, defaultValue, required = false, disabled = false, readOnly = false, label, labelHidden, info, status, resizable = false, autoResize = true, maxLength, displayCharCount = false, hardStop = true, onChange: onChangeProp, onResolveSuggestion, ...restProps } = props;
    const t = useI18n();
    const testIds = useTestIds(testId, getTextAreaTestIds);
    const [charCount, setCharCount] = useState(value?.length ?? defaultValue?.length ?? 0);
    const [focused, setFocused] = useState(false);
    const [textAreaEl, setTextAreaEl] = useElement();
    const textAreaRef = useConsolidatedRef(ref, setTextAreaEl);
    const prevHeightRef = useRef(0);
    const heightPxRef = useRef(0);
    const focusTimeoutRef = useRef(NaN);
    const { announceAssertive } = useLiveLog();
    const controlProp = {};
    // Conditionally render component as controlled/uncontrolled
    if (hasProp(props, 'value')) {
        controlProp.value = value ?? '';
    }
    else if (hasProp(props, 'defaultValue')) {
        controlProp.defaultValue = defaultValue ?? '';
    }
    const onChange = useCallback((e) => {
        if (maxLength !== undefined) {
            if (e.target.value.length >= maxLength) {
                announceAssertive({ message: t('text_area_character_limit'), type: 'warning' });
            }
            else if (maxLength > warnCharCount) {
                const prevCharsRemaining = maxLength - charCount;
                const charsRemaining = maxLength - e.target.value.length;
                if (charsRemaining < warnCharCount && prevCharsRemaining >= warnCharCount) {
                    announceAssertive({
                        message: t('text_area_characters_remaining', [`${warnCharCount}`]),
                        type: 'warning'
                    });
                }
            }
        }
        if (!hardStop || maxLength === undefined || e.target.value.length <= maxLength) {
            onChangeProp?.(e);
            setCharCount(e.target.value.length);
        }
    }, [onChangeProp, charCount, hardStop, maxLength, announceAssertive, t]);
    const recalculateHeight = useCallback(() => {
        if (!textAreaRef.current)
            return;
        const baseHeightPx = heightPxRef.current;
        const currentHeight = parseFloat(getComputedStyle(textAreaRef.current).getPropertyValue('--textarea-height'));
        textAreaRef.current.style.marginBlockEnd =
            currentHeight > baseHeightPx
                ? `calc(min(${textAreaMaxHeight}, ${currentHeight}px) - ${baseHeightPx}px)`
                : '0';
        textAreaRef.current.style.setProperty('--textarea-height', 'auto');
        // Remove scrollbar width to avoid interference with calculation.
        textAreaRef.current.style.setProperty('scrollbar-width', 'none');
        const scrollHeight = textAreaRef.current.scrollHeight;
        const borderWidth = textAreaRef.current.offsetHeight - textAreaRef.current.clientHeight;
        // Set height to auto then scrollHeight + borderWidth to resize TextArea to content
        textAreaRef.current.style.setProperty('--textarea-height', `${scrollHeight + borderWidth}px`);
        textAreaRef.current.style.removeProperty('margin-block-end');
        textAreaRef.current.style.removeProperty('scrollbar-width');
    }, []);
    useLayoutEffect(() => {
        if (!textAreaRef.current)
            return;
        heightPxRef.current = textAreaRef.current.offsetHeight;
    }, [textAreaEl]);
    useEffect(() => {
        if (!textAreaRef.current)
            return;
        if (autoResize) {
            recalculateHeight();
        }
        else {
            textAreaRef.current.style.removeProperty('--textarea-height');
        }
    }, [value, autoResize, recalculateHeight]);
    // Recalculate height when the element becomes visible after being displayed none.
    useEffect(() => {
        if (!textAreaEl || !autoResize)
            return;
        prevHeightRef.current = textAreaEl.offsetHeight;
        const observer = new ResizeObserver(entries => {
            const currentHeight = entries[0].contentRect.height;
            if (prevHeightRef.current === 0 && currentHeight > 0) {
                const textareaHeight = getComputedStyle(textAreaEl)
                    .getPropertyValue('--textarea-height')
                    .trim();
                if (!textareaHeight || textareaHeight === 'auto' || textareaHeight === 'unset') {
                    heightPxRef.current = textAreaEl.offsetHeight;
                }
                recalculateHeight();
            }
            prevHeightRef.current = currentHeight;
        });
        observer.observe(textAreaEl);
        return () => observer.disconnect();
    }, [autoResize, recalculateHeight, textAreaEl]);
    useEffect(() => () => {
        clearTimeout(focusTimeoutRef.current);
    }, []);
    const TextAreaControl = (_jsx(StyledFormControl, { "data-testid": testIds.control, ref: textAreaRef, id: id, "aria-describedby": 
        // Remove when focused to prevent each change in character count from being announced.
        !focused && displayCharCount && typeof maxLength === 'number'
            ? `${id}-charCount`
            : undefined, required: required, disabled: disabled, status: status, readOnly: readOnly, autoResize: autoResize, resizable: autoResize ? false : resizable, maxLength: hardStop ? maxLength : undefined, hasSuggestion: status === 'pending' && !!onResolveSuggestion, ...controlProp, ...restProps, onChange: onChange, onFocus: (e) => {
            // Need to delay so screen readers will include the character count in announcement on initial focus.
            // Shorter delays did not seem to work.
            focusTimeoutRef.current = window.setTimeout(() => {
                setFocused(true);
            }, 250);
            restProps.onFocus?.(e);
        }, onBlur: (e) => {
            clearTimeout(focusTimeoutRef.current);
            setFocused(false);
            restProps.onBlur?.(e);
        }, as: StyledTextArea }));
    return label || displayCharCount ? (_jsx(FormField, { testId: testIds, additionalInfo: additionalInfo, label: label, labelHidden: labelHidden, id: id, readOnly: readOnly, info: info, status: status, charLimitDisplay: displayCharCount && typeof maxLength === 'number' ? (_jsxs(Text, { id: `${id}-charCount`, readOnly: readOnly, variant: 'secondary', children: [maxLength >= 0 ? t('x_of_y', [charCount || '0', maxLength]) : charCount, ' ', _jsx(VisuallyHiddenText, { children: t('characters_typed') })] })) : undefined, required: required, disabled: disabled, onResolveSuggestion: onResolveSuggestion, children: TextAreaControl })) : (TextAreaControl);
});
export default withTestIds(TextArea, getTextAreaTestIds);
//# sourceMappingURL=TextArea.js.map