import * as React from 'react';
import * as d3 from 'd3';
import { copyTransforms, RefinerProps } from './utils';
import { debounce } from 'lodash';

interface NumericalState {
    minValue?: number;
    maxValue?: number;
    minDisplay?: string;
    maxDisplay?: string;
    minWarn?: boolean;
    maxWarn?: boolean;
    isMaxFocused?: boolean;
    isMinFocused?: boolean;
    minInput?: number;
    maxInput?: number;
}

/**
 * Column Component to add filter/refinement functionality to a numerical table column.
 */
export class ColumnNumericalRefinerComponent extends React.Component<
    RefinerProps,
    NumericalState
> {
    transformUpdateMinDebounced;
    transformUpdateMaxDebounced;
    numberFormatter;
    min;
    max;

    constructor(props) {
        super(props);
        if (props.config.numberFormatter) {
            this.numberFormatter = props.config.numberFormatter;
        } else {
            this.numberFormatter = d3.format('.2s');
        }

        this.min = this.props?.data?.extent?.min;
        this.max = this.props?.data?.extent?.max;

        this.state = {
            minDisplay: '',
            maxDisplay: '',
            minWarn: false,
            maxWarn: false,
            isMaxFocused: false,
            isMinFocused: false,
            minInput: undefined,
            maxInput: undefined,
        };

        // debounce updating the transforms with input value
        const delay =
            this.props.transformsDelay === undefined
                ? 1000
                : this.props.transformsDelay;

        const transformUpdateMinHandler = () => {
            this.updateTransforms('minRange', this.state.minInput);
        };
        this.transformUpdateMinDebounced = debounce(
            transformUpdateMinHandler,
            delay
        );

        const transformUpdateMaxHandler = () => {
            this.updateTransforms('maxRange', this.state.maxInput);
        };
        this.transformUpdateMaxDebounced = debounce(
            transformUpdateMaxHandler,
            delay
        );
    }

    updateIsFilteredState() {
        const hasMinInput = this.state.minInput !== undefined,
            hasMaxInput = this.state.maxInput !== undefined,
            isFiltered = hasMinInput || hasMaxInput;
        this.props.setIsFiltered(isFiltered);
    }

    updateTransforms(type, value) {
        let config = this.props.config,
            newTransforms,
            refinementTransform;

        // make a complete copy of the transforms
        newTransforms = copyTransforms(this.props.transforms);

        if (
            Object.prototype.hasOwnProperty.call(
                newTransforms.filters,
                config.columnName
            )
        ) {
            refinementTransform = newTransforms.filters[config.columnName];
        } else {
            refinementTransform = {
                type: 'range',
                column: config.columnName,
            };
        }

        refinementTransform[type] = value;
        newTransforms.filters[config.columnName] = refinementTransform;
        this.props.setTransforms(newTransforms);
    }

    clearInputs = () => {
        this.setState(
            {
                minDisplay: '',
                maxDisplay: '',
                minWarn: false,
                maxWarn: false,
                isMaxFocused: false,
                isMinFocused: false,
                minInput: undefined,
                maxInput: undefined,
            },
            () => {
                this.updateIsFilteredState();
            }
        );
        this.transformUpdateMinDebounced();
        this.transformUpdateMaxDebounced();
    };

    onMinFocus = (isFocused) => {
        let minInput, minDisplay;
        if (!isFocused) {
            minInput = this.state.minInput;
            if (!isNaN(minInput)) {
                if (minInput < this.props?.data?.extent?.min) {
                    minInput = this.props?.data?.extent?.min;
                } else if (
                    !isNaN(this.state.maxInput) &&
                    minInput > this.state.maxInput
                ) {
                    minInput = this.state.maxInput;
                } else if (minInput > this.props?.data?.extent?.max) {
                    minInput = this.props?.data?.extent?.max;
                }
                minDisplay = this.numberFormatter(minInput);
                this.setState(
                    {
                        minDisplay: minDisplay,
                        minInput: minInput,
                        isMinFocused: isFocused,
                        minWarn: false,
                    },
                    () => {
                        this.updateIsFilteredState();
                    }
                );
                this.transformUpdateMinDebounced();
            } else {
                this.setState(
                    {
                        isMinFocused: isFocused,
                        minWarn: false,
                    },
                    () => {
                        this.updateIsFilteredState();
                    }
                );
            }
        } else {
            this.setState(
                {
                    isMinFocused: isFocused,
                },
                () => {
                    this.updateIsFilteredState();
                }
            );
        }
    };

    onMaxFocus = (isFocused) => {
        let maxInput, maxDisplay;
        if (!isFocused) {
            maxInput = this.state.maxInput;
            if (!isNaN(maxInput)) {
                if (maxInput > this.props?.data?.extent?.max) {
                    maxInput = this.props?.data?.extent?.max;
                } else if (
                    !isNaN(this.state.minInput) &&
                    maxInput < this.state.minInput
                ) {
                    maxInput = this.state.minInput;
                } else if (maxInput < this.props?.data?.extent?.min) {
                    maxInput = this.props?.data?.extent?.min;
                }
                maxDisplay = this.numberFormatter(maxInput);
                this.setState(
                    {
                        maxDisplay: maxDisplay,
                        maxInput: maxInput,
                        isMaxFocused: isFocused,
                        maxWarn: false,
                    },
                    () => {
                        this.updateIsFilteredState();
                    }
                );
                this.transformUpdateMaxDebounced();
            } else {
                this.setState(
                    {
                        isMaxFocused: isFocused,
                        maxWarn: false,
                    },
                    () => {
                        this.updateIsFilteredState();
                    }
                );
            }
        } else {
            this.setState(
                {
                    isMaxFocused: isFocused,
                },
                () => {
                    this.updateIsFilteredState();
                }
            );
        }
    };

    onMaxInput = (evt) => {
        const v: number = parseFloat(evt.target.value);
        let maxInput,
            maxDisplay,
            maxWarn = false;
        if (!isNaN(v)) {
            maxInput = v;
            maxWarn =
                maxInput < this.props?.data?.extent?.min ||
                maxInput < this.state.minInput ||
                maxInput > this.props?.data?.extent?.max;
            maxDisplay = this.numberFormatter(v);
        } else {
            maxInput = undefined;
            maxDisplay = 'Max';
            maxWarn = true;
        }

        // debounce updating the transforms with value
        if (!maxWarn || maxInput == undefined) {
            this.transformUpdateMaxDebounced();
        }

        this.setState(
            {
                maxInput: maxInput,
                maxDisplay: maxDisplay,
                maxWarn: maxWarn,
            },
            () => {
                this.updateIsFilteredState();
            }
        );
    };

    onMinInput = (evt) => {
        const v: number = parseFloat(evt.target.value);
        let minInput,
            minDisplay,
            minWarn = false;
        if (!isNaN(v)) {
            minInput = v;
            minWarn =
                minInput < this.props?.data?.extent?.min ||
                minInput > this.props?.data?.extent?.max ||
                minInput > this.state.maxInput;
            minDisplay = this.numberFormatter(v);
        } else {
            minInput = undefined;
            minDisplay = 'Min';
            minWarn = true;
        }

        // debounce updating the transforms with value
        if (!minWarn || minInput == undefined) {
            this.transformUpdateMinDebounced();
        }

        this.setState(
            {
                minInput: minInput,
                minDisplay: minDisplay,
                minWarn: minWarn,
            },
            () => {
                this.updateIsFilteredState();
            }
        );
    };

    /**
     * Render Column Component
     */
    render() {
        const baseClasses: Array<string> = [
            'form-control',
            'mmui-form-control',
            'mmui-range-refiner-input',
        ];
        let minInputClasses: string = baseClasses.join(' ');
        if (this.state.minWarn) {
            minInputClasses += ' is-invalid';
        }
        let maxInputClasses: string = baseClasses.join(' ');
        if (this.state.maxWarn) {
            maxInputClasses += ' is-invalid';
        }

        let minInput, maxInput;
        if (this.state.isMinFocused) {
            minInput = (
                <input
                    className={minInputClasses}
                    name="min-range"
                    type="number"
                    value={this.state.minInput}
                    onChange={this.onMinInput}
                    onBlur={this.onMinFocus.bind(this, false)}
                    placeholder="Min"
                />
            );
        } else {
            minInput = (
                <input
                    className={minInputClasses}
                    name="min-range"
                    type="text"
                    value={this.state.minDisplay}
                    onFocus={this.onMinFocus.bind(this, true)}
                    placeholder="Min"
                />
            );
        }

        if (this.state.isMaxFocused) {
            maxInput = (
                <input
                    className={maxInputClasses}
                    name="max-range"
                    type="number"
                    value={this.state.maxInput}
                    onChange={this.onMaxInput}
                    onBlur={this.onMaxFocus.bind(this, false)}
                    placeholder="Max"
                />
            );
        } else {
            maxInput = (
                <input
                    className={maxInputClasses}
                    name="max-range"
                    type="text"
                    value={this.state.maxDisplay}
                    onFocus={this.onMaxFocus.bind(this, true)}
                    placeholder="Max"
                />
            );
        }

        let defaultLabel;
        if (this.props.displayDefaultLabel) {
            if (
                this.min != this.props?.data?.extent?.min ||
                this.max != this.props?.data?.extent?.max
            ) {
                defaultLabel = (
                    <p className={'py-2 mmui-form-label text-capitalize mb-0 text-muted'}>
                        Default: {this.numberFormatter(this.min)} to {this.numberFormatter(this.max)}
                    </p>
                );
            }
        }

        return (
            <React.Fragment>
                {defaultLabel}
                <div className="d-md-flex">
                    <div className="mmui-mr-1 mmui-mb-1">
                        <label className="mmui-form-label text-capitalize">
                            From{' '}
                            {this.numberFormatter(
                                this.props?.data?.extent?.min
                            )}
                        </label>
                        {minInput}
                    </div>
                    <div>
                        <label className="mmui-form-label text-capitalize">
                            To{' '}
                            {this.numberFormatter(
                                this.props?.data?.extent?.max
                            )}
                        </label>
                        {maxInput}
                    </div>
                </div>
                <div>
                    <button
                        type="button"
                        className="mmui-reset-link btn btn-link pl-0"
                        onClick={this.clearInputs}
                    >
                        Clear
                    </button>
                </div>
            </React.Fragment>
        );
    }
}
