/**
 * The new input element
 *
 * @author Platon Fedorov
 * @date 2021-01-13
 */
import * as React from 'react';

import TextAreaAntd from 'antd/es/input/TextArea';

import * as styles from './input.m.scss';
import {Icon, joinClassNames, safeInvoke, SIZE} from '../../index';
import {createHtmlPropsFilter} from '../../utils/createHtmlPropsFilter';
import {Props} from './Input.types';
import {InputPassword} from './InputPassword';
import {InputMask} from './InputMask';
import {InputTime} from './InputTime';
import {InputNumber} from './InputNumber';
import {Wrapper, WrapperProps} from '../wrapper/Wrapper';
import {getSizeThemeKey} from '../../utils/getSizeThemeKey';
import {SizeClassNames} from '../wrapper/Wrapper.types';
import {InputColorPicker} from './InputColorPicker';

const filterProps = createHtmlPropsFilter<Props, Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'disabled' | 'prefix' |
    'defaultValue' | 'value' | 'onFocus' | 'onBlur'>>([
    'size',
    'title',
    'forwardRef',
    'onPressEnter',
    'onPressEscape',
    'errorMessage',
    'prefix',
    'suffix',
    'allowClear',
    'hasError',
    'isDisabled',
    'onPrefixClick',
    'onSuffixClick'
]);

type InputState = {
    isFocused: boolean;
}

export type InputProps = Props;

export {
    TextAreaAntd
};

export interface ITextAreaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> {
    prefixCls?: string;
    autosize?: boolean;
    autoSize?: boolean;
    onPressEnter?: React.KeyboardEventHandler<HTMLTextAreaElement>;
    'data-qaid'?: string;
}

export class Input extends React.Component<Props, InputState> {
    static TextArea = TextAreaAntd;
    static Password = InputPassword;
    static Mask = InputMask;
    static Time = InputTime;
    static Number = InputNumber;
    static Color = InputColorPicker;

    override state = {
        isFocused: false
    };

    element = React.createRef<HTMLInputElement>();
    wrapperRef = React.createRef<HTMLDivElement>();

    handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        safeInvoke(this.props.onChange, e);
    };

    handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
        if (!this.state.isFocused) {
            this.setState({
                isFocused: true
            });

            safeInvoke(this.props.onFocus, e);
        }
    };

    handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
        if (this.state.isFocused) {
            if (e.relatedTarget === null ||
                (this.wrapperRef.current && e.relatedTarget instanceof Node && !this.wrapperRef.current.contains(e.relatedTarget))
            ) {
                this.setState({
                    isFocused: false
                });

                safeInvoke(this.props.onBlur, e);
            }
        }
    }

    handleWrapperClick = (e: React.MouseEvent) => {
        if (!this.state.isFocused) {
            this.element.current?.focus();
        }
    };

    handleWrapperClickOutside = (e: MouseEvent) => {
        if (this.state.isFocused) {
            const currentElement = this.element.current;

            if (currentElement !== null) {
                currentElement.focus();
                currentElement.blur();
            }
        }
    };

    handleWrapperFocus = () => {
        this.element.current?.focus();
    }

    onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
        switch (e.keyCode) {
            case 13:
                safeInvoke(this.props.onPressEnter, e);
                break;
            case 27:
                safeInvoke(this.props.onPressEscape, e);
                break;
        }

        safeInvoke(this.props.onKeyUp, e);
    }

    createRef () {
        if (this.props.forwardRef) {
            this.element = this.props.forwardRef;
        }

        return this.element;
    }

    handleClearButtonMouseDown: React.MouseEventHandler<HTMLDivElement> = (e) => {
        e.preventDefault();
    };

    handleClearButtonClick: React.MouseEventHandler<HTMLDivElement> = () => {
        const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');

        if (this.element && this.element.current && descriptor && descriptor.set) {
            this.element.current.focus();
            this.element.current.dispatchEvent(new Event('click', {bubbles: true}));
            descriptor.set.call(this.element.current, '');
            this.element.current.dispatchEvent(new Event('input', {bubbles: true}));
        }
    };

    override componentDidMount () {
        if (this.props.autoFocus && this.element.current) {
            this.element.current.focus();
        }
    }

    override componentDidUpdate (prevProps: Readonly<Props>) {
        if (this.props.autoFocus === true && prevProps.autoFocus === false && this.element.current) {
            this.element.current.focus();
        }
    }

    override render () {
        const {size = SIZE.MIDDLE} = this.props;
        const isLogicDisabled = this.props.readOnly || this.props.isDisabled;

        const wrapperProps: WrapperProps = {
            size: size,
            prefix: this.props.prefix,
            suffix: this.props.suffix,
            hasError: this.props.hasError,
            isDisabled: this.props.isDisabled,
            isReadOnly: this.props.readOnly,
            errorMessage: this.props.errorMessage,
            onPrefixClick: this.props.onPrefixClick,
            onSuffixClick: this.props.onSuffixClick,
            isFocused: this.state.isFocused,
            forwardRef: this.wrapperRef,
            onClickOutside: this.handleWrapperClickOutside,
            onClick: this.handleWrapperClick,
            onWrapperFocus: this.handleWrapperFocus,
            tabIndex: isLogicDisabled ? -1 : 0
        };

        const sizeThemeKey = getSizeThemeKey<SizeClassNames>('', size);

        const classContainer = joinClassNames(
            styles.inputContainer,
            styles[sizeThemeKey],
            [styles.readonly, this.props.readOnly === true],
            [styles.disabled, this.props.isDisabled === true]
        );

        return (
            <div className={'ud-input'} data-component-id={'Input'}>
                <Wrapper
                    {...wrapperProps}
                >
                    <div className={classContainer}>
                        {this.renderInput()}
                        {this.renderClearButton()}
                    </div>
                </Wrapper>
            </div>
        );
    }

    renderInput () {
        const inputProps = {
            ...filterProps(this.props),
            ref: this.createRef(),
            onBlur: this.handleBlur,
            onKeyUp: this.onKeyUp,
            onFocus: this.handleFocus,
            onChange: this.handleChange,
            disabled: this.props.isDisabled || this.props.readOnly,
            value: this.props.value !== null ? this.props.value : undefined
        };

        return <input {...inputProps}/>;
    }

    renderClearButton () {
        const {readOnly, isDisabled, size} = this.props;
        const handleClearButtonClick = readOnly || isDisabled ? undefined : this.handleClearButtonClick;
        const handleClearButtonMouseDown = readOnly || isDisabled ? undefined : this.handleClearButtonMouseDown;

        return this.props.allowClear ? (
            <div
                className={styles.allowClearContainer}
                onClick={handleClearButtonClick}
                onMouseDown={handleClearButtonMouseDown}
            >
                <Icon
                    size={size}
                    name={'cross'}
                />
            </div>
        ) : null;
    }
}
