import React, { Component } from 'react';
import {
    SearchIcon,
    SelectInput,
    SearchResultsOutline,
} from '@orchard/frontend-react-components';
import { debounce } from 'lodash';
import {
    CLASSNAME,
    IDNAME,
    TERM_ID,
    TERM_NORESULT,
    TERM_ERROR,
    TERM_PLACEHOLDER,
} from './constants';
import SearchLoadingIndicator from './search-loading-indicator';
import SearchResultOption, { type SearchOption } from './search-result-option';
import SearchResults from './search-results';

let QUERY_SEQ = 0;

interface GlobalSearchProps {
    autoFocus?: boolean;
    idPrefix?: string;
    footerType?: string;
    formatMessage?: (term: string, args?: Record<string, unknown>) => string;
    categories?: string[];
    loading?: boolean;
    options?: SearchOption[];
    error?: boolean;
    queryId?: number;
    onSearch?: (params: { term: string; queryId: number }) => void;
    onSelect?: (option: SearchOption) => void;
    debounceTime?: number;
    idSequence?: () => number;
    openOnFocus?: boolean;
}

interface GlobalSearchState {
    queryId: number;
    queryTerm: string;
}

class GlobalSearch extends Component<GlobalSearchProps, GlobalSearchState> {
    static defaultProps: Partial<GlobalSearchProps> = {
        autoFocus: false,
        footerType: 'link',
        formatMessage: (msg: string) => msg,
        idSequence: () => ++QUERY_SEQ,
        options: [],
        queryId: 0,
        debounceTime: 300,
        openOnFocus: false,
    };

    private _disposed = false;

    constructor(props: GlobalSearchProps) {
        super(props);

        this.state = {
            queryId: 0,
            queryTerm: '',
        };

        const { debounceTime } = props;
        if (debounceTime)
            this.handleInputChange = debounce(
                this.handleInputChange,
                debounceTime,
                { trailing: true }
            );
    }

    shouldComponentUpdate(nextProps: GlobalSearchProps): boolean {
        const { queryId: currentQueryId } = this.state;
        const { queryId } = nextProps;
        return queryId === currentQueryId;
    }

    componentWillUnmount() {
        this._disposed = true;
    }

    formatMessage = (term: string, args?: Record<string, unknown>): string => {
        const { formatMessage } = this.props;
        return formatMessage!(`${TERM_ID}.${term}`, args);
    };

    handleInputChange = (inputValue: string): void => {
        if (this._disposed) return;

        const term = inputValue.trim();
        const { queryTerm } = this.state;
        if (term === queryTerm) return;

        const { onSearch, idSequence } = this.props;
        const queryId = idSequence!();

        this.setState(
            { queryId, queryTerm: term },
            () => onSearch && onSearch({ term, queryId })
        );
    };

    handleChange = (selectedValue: unknown): void => {
        if (this._disposed) return;

        const { onSelect, options = [] } = this.props;

        if (onSelect) {
            const [option] = options.filter(
                ({ value }) => value === selectedValue
            );
            if (option) onSelect(option);
        }
    };

    handleFooterClick = (selectedValue: SearchOption): void => {
        if (this._disposed) return;

        const { onSelect } = this.props;
        if (onSelect) onSelect(selectedValue);
    };

    noResultsText(): React.ReactNode {
        const { loading, error, categories } = this.props;
        const { queryTerm } = this.state;

        if (error)
            return (
                <div className="error-text">
                    {this.formatMessage(TERM_ERROR)}
                </div>
            );

        if (loading)
            return (
                <SearchLoadingIndicator
                    formatMessage={this.formatMessage}
                    categories={categories}
                />
            );

        if (queryTerm)
            return (
                <div className="noresults-container">
                    <SearchResultsOutline />
                    <div className="noresults-text">
                        {this.formatMessage(TERM_NORESULT, {
                            query: queryTerm,
                        })}
                    </div>
                </div>
            );

        return null;
    }

    render() {
        const {
            autoFocus,
            footerType,
            idPrefix,
            options,
            loading,
            openOnFocus,
        } = this.props;

        return (
            <div
                className={CLASSNAME}
                id={idPrefix ? `${idPrefix}-${IDNAME}` : undefined}
            >
                <span>
                    <SelectInput
                        autofocus={autoFocus}
                        labelKey="title"
                        valueKey="value"
                        options={loading ? [] : options}
                        isLoading={loading}
                        filterOptions={false}
                        placeholder={this.formatMessage(TERM_PLACEHOLDER)}
                        noResultsText={this.noResultsText()}
                        onInputChange={this.handleInputChange}
                        onChange={this.handleChange}
                        optionComponent={SearchResultOption}
                        openOnFocus={openOnFocus}
                        menuRenderer={(
                            props: React.ComponentProps<typeof SearchResults>
                        ) => (
                            <SearchResults
                                {...props}
                                footerType={footerType}
                                onFooterClick={this.handleFooterClick}
                                formatMessage={this.formatMessage}
                            />
                        )}
                    />
                    <span>
                        <SearchIcon className={`${CLASSNAME}-search`} />
                    </span>
                </span>
            </div>
        );
    }
}

export default GlobalSearch;
