import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import { Translation } from "react-i18next"
import { SliderPicker } from "react-color"

import { Icons, createIconRender } from "@components/Icons"

import PerformanceLog from "@classes/PerformanceLog"

import "./index.less"

function shouldUseHorizontalLayout(type) {
    switch (type) {
        case "switch":
            return true
        case "button":
            return true
        default:
            return false
    }
}

export const SettingsComponents = {
    button: {
        component: antd.Button,
        props: (_this) => {
            return {
                onClick: (event) => _this.onUpdateItem(event)
            }
        }
    },
    switch: {
        component: antd.Switch,
        props: (_this) => {
            return {
                onChange: (event) => _this.onUpdateItem(event)
            }
        }
    },
    slider: {
        component: antd.Slider,
        props: (_this) => {
            return {
                onChange: (event) => _this.onUpdateItem(event)
            }
        }
    },
    input: {
        component: antd.Input,
        props: (_this) => {
            return {
                defaultValue: _this.state.value,
                onChange: (event) => _this.onUpdateItem(event.target.value),
                onPressEnter: (event) => _this.dispatchUpdate(event.target.value)
            }
        }
    },
    textarea: {
        component: antd.Input.TextArea,
        props: (_this) => {
            return {
                defaultValue: _this.state.value,
                onChange: (event) => _this.onUpdateItem(event.target.value),
                onPressEnter: (event) => _this.dispatchUpdate(event.target.value)
            }
        }
    },
    inputnumber: {
        component: antd.InputNumber,
    },
    select: {
        component: antd.Select,
        props: (_this) => {
            return {
                onChange: (event) => {
                    console.log(event)
                    _this.onUpdateItem(event)
                }
            }
        }
    },
    slidercolorpicker: {
        component: SliderPicker,
        props: (_this) => {
            return {
                onChange: (color) => {
                    _this.setState({
                        componentProps: {
                            ..._this.state.componentProps,
                            color
                        }
                    })
                },
                onChangeComplete: (color) => {
                    _this.onUpdateItem(color.hex)
                },
                color: _this.state.value
            }
        }
    },
}

export default class SettingItemComponent extends React.PureComponent {
    state = {
        value: null,
        debouncedValue: null,

        componentProps: Object(),
        loading: true,
    }

    perf = new PerformanceLog(`Init ${this.props.setting.id}`, {
        disabled: true
    })

    componentType = null

    componentRef = React.createRef()

    componentDidMount = async () => {
        if (typeof this.props.setting.component === "string") {
            this.componentType = String(this.props.setting.component).toLowerCase()
        }

        await this.initialize()
    }

    componentWillUnmount = () => {
        this.setState({
            value: null,
            componentProps: Object(),
        })

        if (typeof this.props.setting.dependsOn === "object") {
            for (const key in this.props.setting.dependsOn) {
                window.app.eventBus.off(`setting.update.${key}`)
            }
        }
    }

    generateInhertedProps = () => {
        if (!SettingsComponents[this.componentType]) {
            return {}
        }

        if (typeof SettingsComponents[this.componentType].props === "function") {
            const inhertedProps = SettingsComponents[this.componentType].props(this)

            return inhertedProps
        }

        return {}
    }

    toggleLoading = (to) => {
        if (typeof to === "undefined") {
            to = !this.state.loading
        }

        this.setState({
            loading: to
        })
    }

    initialize = async () => {
        this.perf.start(`init tooks`)

        this.toggleLoading(true)

        if (this.props.setting.storaged) {
            this.perf.start(`get value from storaged`)

            await this.setState({
                value: window.app.cores.settings.get(this.props.setting.id),
            })

            this.perf.end(`get value from storaged`)
        }

        if (typeof this.props.setting.defaultValue === "function") {
            this.perf.start(`execute default value fn`)

            this.toggleLoading(true)

            this.setState({
                value: await this.props.setting.defaultValue(this.props.ctx)
            })

            this.toggleLoading(false)

            this.perf.end(`execute default value fn`)
        }

        if (typeof this.props.setting.dependsOn === "object") {
            this.perf.start(`register dependsOn events`)

            Object.keys(this.props.setting.dependsOn).forEach((key) => {
                // create a event handler to watch changes
                window.app.eventBus.on(`setting.update.${key}`, () => {
                    this.setState({
                        componentProps: {
                            ...this.state.componentProps,
                            disabled: this.checkDependsValidation()
                        }
                    })
                })
            })

            this.perf.end(`register dependsOn events`)

            this.perf.start(`check depends validation`)

            // by default check depends validation
            this.setState({
                componentProps: {
                    ...this.state.componentProps,
                    disabled: this.checkDependsValidation()
                }
            })

            this.perf.end(`check depends validation`)
        }

        if (typeof this.props.setting.listenUpdateValue === "string") {
            this.perf.start(`listen "on update" value`)

            window.app.eventBus.on(`setting.update.${this.props.setting.listenUpdateValue}`, () => {
                this.setState({
                    value: window.app.cores.settings.get(this.props.setting.id)
                })
            })

            this.perf.end(`listen "on update" value`)
        }

        if (this.props.setting.reloadValueOnUpdateEvent) {
            this.perf.start(`Reinitializing setting [${this.props.setting.id}]`)

            window.app.eventBus.on(this.props.setting.reloadValueOnUpdateEvent, () => {
                console.log(`Reinitializing setting [${this.props.setting.id}]`)
                this.initialize()
            })

            this.perf.end(`Reinitializing setting [${this.props.setting.id}]`)
        }

        this.toggleLoading(false)

        this.perf.end(`init tooks`)

        this.perf.finally()
    }

    dispatchUpdate = async (updateValue) => {
        if (typeof this.props.setting.onUpdate === "function") {
            try {
                const result = await this.props.setting.onUpdate(updateValue)

                if (result) {
                    updateValue = result
                }
            } catch (error) {
                console.error(error)

                if (error.response.data.error) {
                    app.message.error(error.response.data.error)
                } else {
                    app.message.error(error.message)
                }

                return false
            }
        }

        const storagedValue = window.app.cores.settings.get(this.props.setting.id)

        if (typeof updateValue === "undefined") {
            updateValue = !storagedValue
        }

        if (this.props.setting.storaged) {
            await window.app.cores.settings.set(this.props.setting.id, updateValue)

            if (typeof this.props.setting.beforeSave === "function") {
                await this.props.setting.beforeSave(updateValue)
            }
        }

        if (typeof this.props.setting.emitEvent !== "undefined") {
            if (typeof this.props.setting.emitEvent === "string") {
                this.props.setting.emitEvent = [this.props.setting.emitEvent]
            }

            let emissionPayload = updateValue

            if (typeof this.props.setting.emissionValueUpdate === "function") {
                emissionPayload = this.props.setting.emissionValueUpdate(emissionPayload)
            }

            for await (const event of this.props.setting.emitEvent) {
                app.eventBus.emit(event, emissionPayload)
            }
        }

        if (this.props.setting.noUpdate) {
            return false
        }

        // reset debounced value
        if (this.props.setting.debounced) {
            await this.setState({
                debouncedValue: null
            })
        }

        if (this.componentRef.current) {
            if (typeof this.componentRef.current.onDebounceSave === "function") {
                await this.componentRef.current.onDebounceSave(updateValue)
            }
        }

        if (typeof this.props.onUpdate === "function") {
            await this.props.onUpdate(updateValue)
        }

        // finaly update value
        await this.setState({
            value: updateValue
        })

        return updateValue
    }

    onUpdateItem = async (updateValue) => {
        this.setState({
            value: updateValue
        })

        if (this.props.setting.debounced) {
            return await this.setState({
                debouncedValue: updateValue
            })
        }

        return await this.dispatchUpdate(updateValue)
    }

    checkDependsValidation = () => {
        return !Boolean(Object.keys(this.props.setting.dependsOn).every((key) => {
            const storagedValue = window.app.cores.settings.get(key)

            console.debug(`Checking validation for [${key}] with now value [${storagedValue}]`)

            if (typeof this.props.setting.dependsOn[key] === "function") {
                return this.props.setting.dependsOn[key](storagedValue)
            }

            return storagedValue === this.props.setting.dependsOn[key]
        }))
    }

    computeSwitchEnablerDefault = () => {
        if (typeof this.props.setting.switchDefault === "function") {
            return this.props.setting.switchDefault()
        }

        return this.props.setting.switchDefault
    }

    render() {
        if (!this.props.setting) {
            console.error(`Item [${this.props.setting.id}] has no an setting!`)
            return null
        }

        if (!this.props.setting.component) {
            console.error(`Item [${this.props.setting.id}] has no an setting component!`)
            return null
        }

        let finalProps = {
            ...this.state.componentProps,

            ...this.generateInhertedProps(),
            ...this.props.setting.props,

            ctx: {
                updateCurrentValue: (updateValue) => this.setState({
                    value: updateValue
                }),
                getCurrentValue: () => this.state.value,
                currentValue: this.state.value,
                dispatchUpdate: this.dispatchUpdate,
                onUpdateItem: this.onUpdateItem,
                processedCtx: this.props.ctx,
            },
            ref: this.componentRef,

            // set values
            checked: this.state.value,
            value: this.state.value,

            size: app.isMobile ? "large" : "default"
        }

        if (this.props.setting.children) {
            finalProps.children = this.props.setting.children
        }

        if (app.isMobile) {
            finalProps.size = "large"
        }

        const Component = SettingsComponents[String(this.props.setting.component).toLowerCase()]?.component ?? this.props.setting.component

        return <div
            key={this.props.setting.id}
            id={this.props.setting.id}
            className={classnames(
                "setting_item",
                {
                    ["usePadding"]: this.props.setting.usePadding ?? true,
                    ["useHorizontal"]: this.props.setting.layout ?? shouldUseHorizontalLayout(String(this.props.setting.component).toLowerCase())
                })
            }
        >
            <div className="setting_item_header">
                <div className="setting_item_info">
                    <div className="setting_item_header_title">
                        <h1>
                            {
                                createIconRender(this.props.setting.icon)
                            }
                            <Translation>
                                {(t) => t(this.props.setting.title ?? this.props.setting.id)}
                            </Translation>
                        </h1>

                        {
                            this.props.setting.experimental && <antd.Tag>Experimental</antd.Tag>
                        }
                    </div>
                    <div className="setting_item_header_description">
                        <p>
                            <Translation>
                                {(t) => t(this.props.setting.description)}
                            </Translation>
                        </p>
                    </div>
                </div>

                <div className="setting_item_header_actions">
                    {
                        this.props.setting.extraActions && this.props.setting.extraActions.map((action, index) => {
                            if (typeof action === "function") {
                                return React.createElement(action, {
                                    ctx: {
                                        updateCurrentValue: (updateValue) => this.setState({
                                            value: updateValue
                                        }),
                                        getCurrentValue: () => this.state.value,
                                        currentValue: this.state.value,
                                        dispatchUpdate: this.dispatchUpdate,
                                        onUpdateItem: this.onUpdateItem,
                                        processedCtx: this.props.ctx
                                    },
                                })
                            }

                            const handleOnClick = () => {
                                if (action.onClick) {
                                    action.onClick(finalProps.ctx)
                                }
                            }

                            return <antd.Button
                                key={action.id}
                                id={action.id}
                                onClick={handleOnClick}
                                icon={action.icon && createIconRender(action.icon)}
                                type={action.type ?? "round"}
                                disabled={this.props.setting.disabled}
                            >
                                {action.title}
                            </antd.Button>
                        })
                    }

                    {
                        typeof this.props.setting.onEnabledChange === "function" && <antd.Switch
                            defaultChecked={this.computeSwitchEnablerDefault()}
                            onChange={this.props.setting.onEnabledChange}
                        />
                    }
                </div>
            </div>

            <div className="setting_item_content">
                <>
                    {
                        !this.state.loading && React.createElement(Component, finalProps)
                    }
                    {
                        this.state.loading && <antd.Spin />
                    }
                    {
                        this.state.debouncedValue && <antd.Button
                            type="round"
                            icon={<Icons.FiSave />}
                            onClick={async () => await this.dispatchUpdate(this.state.debouncedValue)}
                        >
                            Save
                        </antd.Button>
                    }
                </>
            </div>
        </div>
    }
}