import * as React from 'react';

/**
 * Component modes
 */

export const COMPONENT_MODE_INITIAL = 'initial';
export const COMPONENT_MODE_LOADING = 'loading';
export const COMPONENT_MODE_ERROR = 'error';
export const COMPONENT_MODE_EMPTY = 'empty';
export const COMPONENT_MODE_OK = 'ok';
export const COMPONENT_MODE_CUSTOM = 'custom';

export interface iSetToStore {
    (path: any, newState: any): void;
}

export interface iMergeToStore {
    (path: any, newState: any): void;
}

export interface iSetComponentValue {
    (name: string, key: string | Array<string>, value: any): void;
}

export interface Props {
    state?: any;
    setComponentValue?: iSetComponentValue;
}

export interface State {}

/**
 * React Component Base class with loadingImage and helper methods to store/extract data from either Redux store.
 * @deprecated Use MmuiCommonComponent
 */
export class CommonComponent<P = Props, S = State> extends React.Component<
    P,
    S
> {
    name;
    loadingImage;

    constructor(props: P) {
        super(props);
        // Note: need to pass staticUrl when initiating the store for each case
        // @ts-ignore

        this.loadingImage = this.props.staticUrl + 'images/mm-loading.gif';
    }

    getState() {
        // only get this component's state.component, defined by the name attribute, which needs to be unique
        // @ts-ignore
        return this.props.state.component[this.name];
    }
    // get particular state.data
    getData(key) {
        // @ts-ignore
        return this.props.state.data[key];
    }

    getFilterFormObj() {
        // @ts-ignore
        return this.props.state.filterObj;
    }

    setMode(mode) {
        // like 'loading'=> spinning image, 'error' 'ok' 'empty', constant defined at the top
        // use `bindActionCreators` to bind action function `setComponentValue` to props
        // @ts-ignore
        this.props.setComponentValue(this.name, 'mode', mode);
    }
}

/**
 * React Chart component with common chart member variable.
 */
export class ChartComponent<P = unknown, S = unknown> extends CommonComponent<
    P,
    S
> {
    chart;
    chartId;
    chartSelector;

    constructor(props: P) {
        super(props);
    }
}

/**
 * React Table Component with empty row and error row render helper methods.
 * @deprecated Use MmuiTableComponent
 */
export class TableComponent<P = unknown, S = unknown> extends CommonComponent<
    P,
    S
> {
    constructor(props: P) {
        super(props);
    }

    createEmptyRow(cols) {
        return (
            <tr>
                <td colSpan={cols} className="text-center font-weight-bold">
                    No data available
                </td>
            </tr>
        );
    }

    createErrorRow(cols) {
        return (
            <tr>
                <td colSpan={cols} className="text-center font-weight-bold">
                    Unfortunately an error occurred.
                </td>
            </tr>
        );
    }
}

export interface MmuiProps {
    elmtId?: string; // html element id of the Component
    name?: string; //  if useStore is true, this indicates where the state is read/written to/from the store
    store?: any; // Redux store
    setToStore?: iSetToStore; // function to set objects into the store
    mergeToStore?: iMergeToStore; // function to merge values into the store
    useStore?: boolean; // should the Component use its local state or the injected Redux store
    payload?: any; // data payload to power the content render of the Component
    // initializeState: if true, the Component State will be initialized with the result returned by getInitialComponentState,
    //  override the getInitialComponentState in your class that inherits from MmuiCommonComponent
    initializeState?: boolean;
    staticUrl?: string; // Url to static resources
}

export interface MmuiState {
    mode?: any; // component mode constant i.e. COMPONENT_MODE_*
    payload?: any; // data payload to power the content render of the Component
}

/**
 * React Component Base class with loadingImage and helper methods to store/extract data from either Redux store or local state.
 */
export class MmuiCommonComponent<
    P extends MmuiProps,
    S extends MmuiState
> extends React.Component<P, S> {
    protected id;
    protected static id_counter = 0;
    name: string;
    loadingImage: string;

    // flag to determine if you want to use the Components local state or Redux store injected via the props
    useStore: boolean;

    /**
     * Generate a unique id for this component instance
     */
    protected getId(): number {
        if (this.id === undefined) {
            this.id = MmuiCommonComponent.id_counter;
            MmuiCommonComponent.id_counter++;
        }
        return this.id;
    }

    constructor(props) {
        super(props);

        this.name = props.name;
        this.useStore = false;

        if (this.props.useStore) {
            this.useStore = true;
        }

        if (this.props.staticUrl) {
            this.loadingImage = this.props.staticUrl + 'images/mm-loading.gif';
        } else if (this.props.store && this.props.store.staticUrl) {
            this.loadingImage =
                this.props.store.staticUrl + 'images/mm-loading.gif';
        }

        if (this.props.initializeState) {
            const state = this.getInitialComponentState();
            this.setComponentState(state);
        }
    }

    /**
     * A method to return the Component's initial state.
     * Will be called in constructor if this.props.initializeState is true.
     * Override in a child class that inherits from this class.
     * @return this component's initial state.
     */
    getInitialComponentState(): any {
        return {};
    }

    /**
     * Get the Component's state
     * @param fromStore - overrides the useStore property
     */
    getComponentState(fromStore = false) {
        if (fromStore || this.useStore) {
            return this.props.store.component[this.name];
        }

        return this.state;
    }

    /**
     * Get the Component's state
     * @param fromStore - overrides the useStore property
     *
     * @deprecated Use getComponentState instead
     */
    getState(fromStore = false) {
        return this.getComponentState(fromStore);
    }

    /**
     * Set a value into the Component's state
     * @param newState - new component state
     * @param toStore - overrides the useStore property
     */
    setComponentState(newState: any, toStore = false) {
        // newState.updatedAt = new Date();

        if (toStore || this.useStore) {
            const path = ['component', this.name];
            this.props.setToStore(path, newState);
        } else {
            // TODO: Do not set the state diurectly.
            // eslint-disable-next-line react/no-direct-mutation-state
            this.state = newState;
        }
    }

    /**
     * Set a value into the Component's state
     * @param updateState - component state update
     * @param toStore - overrides the useStore property
     */
    mergeComponentState(mergeState: any, toStore = false) {
        // newState.updatedAt = new Date();

        if (toStore || this.useStore) {
            const path = ['component', this.name];
            this.props.mergeToStore(path, mergeState);
        } else {
            this.setState(mergeState);
        }
    }

    /**
     * Set a value into the Component's state
     * @param key - key to set value at
     * @param value - value to set
     * @param toStore - overrides the useStore property
     */
    setComponentStateValue(key: string, value: any, toStore = false) {
        const newState: any = {};
        newState[key] = value;
        newState.updatedAt = new Date();

        if (toStore || this.useStore) {
            const path = ['component', this.name];
            this.props.mergeToStore(path, newState);
        } else {
            this.setState(newState);
        }
    }

    /**
     * Set a value into the Component's state
     * @param key - key to set value at
     * @param value - value to set
     * @param toStore - overrides the useStore property
     *
     * @deprecated Use setComponentStateValue instead
     */
    setStateValue(key: string, value: string, toStore = false) {
        this.setComponentStateValue(key, value, toStore);
    }

    /**
     * Set a new mode value into the Component's state.
     * @param mode - component mode constant i.e. COMPONENT_MODE_*
     * @param toStore - overrides the useStore property
     */
    setOperation(operation: string, toStore = false) {
        this.setComponentStateValue('operation', operation, toStore);
    }

    /**
     * Get payload data from state or store.
     * @param fromStore - overrides the useStore property
     * @param dKey - optional key to index into the Redux store's data object
     */
    getPayloadData(fromStore = false, dKey?: string) {
        let payload,
            dataKey = this.name;

        if (dKey) {
            dataKey = dKey;
        }

        if (fromStore || this.useStore) {
            return this.props.store.data[dataKey];
        }

        payload = this.props.payload;

        if (payload === undefined || payload === null) {
            payload = this.state.payload;
        }

        return payload;
    }
}

/**
 * React Table Component with empty row and error row render helper methods.
 */
export class MmuiTableComponent<
    P extends MmuiProps,
    S extends MmuiState
> extends MmuiCommonComponent<P, S> {
    constructor(props: MmuiProps) {
        super(props);
    }

    createEmptyRow(cols) {
        return (
            <tr>
                <td colSpan={cols} className="text-center font-weight-bold">
                    No data available
                </td>
            </tr>
        );
    }

    createErrorRow(cols) {
        return (
            <tr>
                <td colSpan={cols} className="text-center font-weight-bold">
                    Unfortunately an error occurred.
                </td>
            </tr>
        );
    }
}

/**
 * React Component to Overlay a parent Component with a mode indicator.
 * make sure when initiating the ComponentModeWindow instance, provide attributes: mode, hasEmptyMode, hasErrorMode, loadingGraphicUrl
 */
export interface ComponentModeWindowProps {
    mode?: any;
    hasEmptyMode?: any;
    hasErrorMode?: any;
    loadingGraphicUrl?: any;
    reportIssueUrl?: string;
    customIconClass?: string;
    customMessage?: string;
}

/**
 * React Component to Overlay MmuiChartComponent and MmuiCraftedTableComponent to output a html message when mode is not OK (loading, error, empty)
 */
export class ComponentModeWindow extends React.Component<
    ComponentModeWindowProps,
    State
> {
    constructor(props: ComponentModeWindowProps) {
        super(props);
    }
    render() {
        let content = null;

        let reportIssueUrlContent = null;

        if (this.props.reportIssueUrl) {
            reportIssueUrlContent = (
                <div className="mmui-err-link-margin">
                    <a
                        id="issue_url"
                        className="feedbackTrigger"
                        target="_blank"
                        href={this.props.reportIssueUrl}
                        rel="noreferrer"
                    >
                        Report Issue
                    </a>
                </div>
            );
        }

        if (this.props.mode === COMPONENT_MODE_CUSTOM) {
            content = (
                <div className="component-mode-window">
                    <div className="mmui-mode-container">
                        <div className="mmui-err-icon mmui-client-err-icon">
                            <i className={this.props.customIconClass}></i>
                        </div>
                        <p className="mmui-body-copy">
                            {this.props.customMessage}
                        </p>
                    </div>
                </div>
            );
        } else if (this.props.mode === COMPONENT_MODE_LOADING) {
            content = (
                <div className="component-mode-window">
                    <div className="loading-indicator">
                        <img
                            alt={'Loading'}
                            src={this.props.loadingGraphicUrl}
                        />
                        <span className="sr-only">Loading...</span>
                    </div>
                </div>
            );
        } else if (
            this.props.hasEmptyMode &&
            this.props.mode === COMPONENT_MODE_EMPTY
        ) {
            content = (
                <div className="component-mode-window">
                    <div className="mmui-mode-container">
                        <div className="mmui-err-icon mmui-client-err-icon">
                            <i className="fas fa-search"></i>
                        </div>
                        <p className="mmui-body-copy">
                            No data available based on your selections
                        </p>
                        <p className="mmui-sm-copy">
                            Try modifying any conflicting selections or reducing
                            selections.
                        </p>
                    </div>
                </div>
            );
        } else if (
            this.props.hasErrorMode &&
            this.props.mode === COMPONENT_MODE_ERROR
        ) {
            content = (
                <div className="component-mode-window">
                    <div className="mmui-mode-container">
                        <div className="mmui-err-icon mmui-client-err-icon">
                            <i className="fas fa-wrench"></i>
                        </div>
                        <p className="mmui-body-copy">
                            Something went wrong on our end
                        </p>
                        <p className="mmui-sm-copy">
                            Try again with some changes.
                        </p>
                        {reportIssueUrlContent}
                    </div>
                </div>
            );
        }

        return content;
    }
}
