import { global } from "../../utility";
import { createElement } from "../../functions";
import { createIcon } from "../icon";
import { createCheckbox, createRadiobox } from "../checkbox";
// import { setTooltip } from "../tooltip";
import { Dropdown } from "../dropdown";
import { convertCssStyle } from "../extension";
import { createDateInput, formatDate, setDateValue, getDateValue } from "../date";
// definition
import { GridRowItem, DropdownOptions, GridColumnDefinition, GridSourceItem, GridItemWrapper, Grid } from "./grid";

/**
 * @ignore
 * @callback DropExpandedCallback
 * @param {GridRowItem} item - 行数据对象
 * @param {Dropdown} drop - 下拉框对象
 * @this GridColumnDefinition
 */

/**
 * 列定义基类
 * 
 * _函数调用流程图示_<br/>
 * <img src="./assets/column-refresh.jpg" alt="Column Refresh"/>
 * @class
 * @static
 * @hideconstructor
 */
export class GridColumn {
    /**
     * 该属性返回 `true` 后，在任意事件中修改行包装对象的 `__editing` 值，则会在行列元素变动时及时触发 [onChanged]{@linkcode GridColumnDefinition#onChanged} 方法，避免例如文本框和日期框还未触发事件就被移除元素而导致的问题
     * @member
     * @name GridColumn.editing
     * @readonly
     * @type {boolean}
     * @see 更多例子参考 {@linkcode GridInputColumn} {@linkcode GridDateColumn} 中的代码实现
     */

    /**
     * 标记该类型是否可编辑
     * @member
     * @name GridColumn.canEdit
     * @readonly
     * @type {boolean}
     */

    /**
     * 标记该类型是否支持列头批量操作
     * @member
     * @name GridColumn.headerEditing
     * @readonly
     * @type {boolean}
     */

    /**
     * 创建显示单元格时调用的方法
     * @param {GridColumnDefinition} col - 列定义对象
     * @param {number} index - 行元素索引（需要配合 [startIndex]{@linkcode Grid#startIndex} 相加得到真实数据索引）
     * @param {Grid} grid - Grid 实例
     * @returns {HTMLElement} 返回创建的单元格元素
     * @virtual
     */
    static create() {
        return createElement('span');
    }

    /**
     * 创建编辑单元格时调用的方法
     * 
     * 元素修改后设置行包装对象的 `__editing` 后，支持在离开编辑状态时及时触发 [leaveEdit]{@linkcode GridColumn.leaveEdit} 方法<br/>
     * 更多例子参考代码中 {@linkcode GridDropdownColumn} 的实现。
     * @method
     * @name GridColumn.createEdit
     * @param {Function} trigger - 编辑事件回调函数
     * @param {any} trigger.e - 该参数会传递给 [getValue]{@linkcode GridColumn.getValue} 方法
     * @param {GridColumnDefinition} col - 列定义对象
     * @param {HTMLElement} [container] - 父容器元素
     * @param {GridItemWrapper} [wrapper] - 行包装对象，其 `values` 属性为行数据对象
     * @returns {HTMLElement} 返回创建的编辑状态的单元格元素
     * @virtual
     */

    /**
     * 创建列头时调用的方法
     * @method
     * @name GridColumn.createCaption
     * @param {GridColumnDefinition} col - 列定义对象
     * @returns {HTMLElement} 返回创建的列头元素
     * @virtual
     */

    /**
     * 获取用于判断文本大小的元素
     * @method
     * @name GridColumn.getElement
     * @param {HTMLElement} element - 单元格主内容元素
     * @returns {HTMLElement} 返回文本元素
     * @virtual
     */

    /**
     * 获取编辑状态单元格值时调用的方法
     * @method
     * @name GridColumn.getValue
     * @param {any} e - 由 [createEdit]{@linkcode GridColumn.createEdit} 方法中 `trigger` 函数传递来的对象
     * @param {GridColumnDefinition} col - 列定义对象
     * @returns {(string | boolean | number)} 返回单元格的值
     * @virtual
     */

    /**
     * 设置单元格值时调用的方法
     * @param {HTMLElement} element - 单元格元素
     * @param {(string | boolean | number)} val - 待设置的单元格值
     * @param {GridItemWrapper} wrapper - 行包装对象
     * @param {GridColumnDefinition} col - 列定义对象
     * @param {Grid} grid - Grid 对象
     * @virtual
     */
    static setValue(element, val) {
        element.innerText = val;
    }

    /**
     * 设置单元格样式时调用的方法
     * @param {HTMLElement} element - 单元格元素
     * @param {object} style - 样式对象
     * @virtual
     */
    static setStyle(element, style) {
        // for (let css of Object.entries(style)) {
        //     element.style.setProperty(css[0], css[1]);
        // }
        element.style.cssText = convertCssStyle(style);
    }

    /**
     * 设置单元格类名时调用的方法
     * @param {HTMLElement} element - 单元格元素
     * @param {string} name - 要设置的类名
     * @virtual
     */
    static setClass(element, name) {
        element.className = name ?? '';
    }

    /**
     * 设置单元格可用性时调用的方法
     * @param {HTMLElement} element - 单元格元素
     * @param {boolean} enabled - 启用值，为 `false` 时代表禁用
     * @param {boolean} editing - 是否处于编辑状态
     * @virtual
     */
    static setEnabled(element, enabled) {
        const tooltip = element.querySelector('.ui-tooltip-wrapper');
        if (tooltip != null) {
            tooltip.style.display = enabled === false ? 'none' : '';
        }
    }

    /**
     * 单元格编辑状态发生改变时调用的方法
     * @method
     * @name GridColumn.setEditing
     * @param {HTMLElement} element - 单元格元素
     * @param {boolean} editing - 是否处于编辑状态
     * @virtual
     */

    /**
     * 单元格离开编辑元素时调用的方法，需要由行包装对象的 `__editing` 来确定是否触发。
     * @method
     * @name GridColumn.leaveEdit
     * @param {HTMLElement} element - 单元格元素
     * @param {HTMLElement} container - 父容器元素
     * @virtual
     */

    /**
     * @ignore
     */
    static toString() { return 'GridCommon' }

    /**
     * @ignore
     * @param {string} key 
     * @param {GridItemWrapper} wrapper 
     * @param {any} value 
     */
    static _changeValue(key, wrapper, value) {
        const val = wrapper.values[key] ?? null;
        const hasValue = val != null && Object.prototype.hasOwnProperty.call(val, 'Value');
        if (wrapper.__editing == null) {
            wrapper.__editing = {
                [key]: hasValue ? val.Value : val
            }
        } else if (!Object.prototype.hasOwnProperty.call(wrapper.__editing, key)) {
            wrapper.__editing[key] = hasValue ? val.Value : val;
        }
        if (hasValue) {
            val.Value = value;
            if (Object.prototype.hasOwnProperty.call(val, 'DisplayValue')) {
                val.DisplayValue = value;
            }
        } else {
            wrapper.values[key] = value;
        }
    }
}

/**
 * 单行文本输入列
 * @class
 * @static
 * @extends GridColumn
 * @hideconstructor
 */
export class GridInputColumn extends GridColumn {
    static get editing() { return true };
    static get canEdit() { return true };

    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} col 
     * @param {HTMLElement} _container 
     * @param {GridItemWrapper} wrapper 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, col, _container, wrapper) {
        const input = createElement('input');
        input.setAttribute('type', 'text');
        input.addEventListener('input', () => super._changeValue(col.key, wrapper, input.value));
        input.addEventListener('change', trigger);
        return input;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} val 
     */
    static setValue(element, val) {
        if (element.tagName !== 'INPUT') {
            super.setValue(element, val);
        } else {
            element.value = val;
        }
    }

    /**
     * @ignore
     * @param {Event} e 
     * @returns {string}
     */
    static getValue(e) { return e.target.value }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} enabled 
     */
    static setEnabled(element, enabled) {
        super.setEnabled(element, enabled);
        element.disabled = enabled === false;
    }

    /**
     * @ignore
     */
    static toString() { return 'GridInput' }
}

/**
 * 多行文本输入列
 * @class
 * @static
 * @extends GridInputColumn
 * @hideconstructor
 * @ignore
 */
export class GridTextColumn extends GridInputColumn {
    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} col 
     * @param {HTMLElement} _container 
     * @param {GridItemWrapper} wrapper 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, col, _container, wrapper) {
        const input = createElement('textarea');
        input.addEventListener('input', () => super._changeValue(col.key, wrapper, input.value));
        input.addEventListener('change', trigger);
        return input;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} val 
     * @param {GridItemWrapper} _wrapper 
     * @param {GridColumnDefinition} _col 
     * @param {Grid} grid 
     */
    static setValue(element, val, _wrapper, _col, grid) {
        if (element.tagName !== 'TEXTAREA') {
            super.setValue(element, val);
        } else {
            element.value = val;
            if (val != null) {
                const lines = String(val).split('\n').length;
                element.style.height = `${lines * grid.lineHeight + 12}px`;
            }
            // TODO: bad performance
        }
    }

    /**
     * @ignore
     */
    static toString() { return 'GridText' }
}

const SymbolDropdown = Symbol.for('ui-dropdown');

/**
 * 下拉选择列
 * @class
 * @static
 * @extends GridColumn
 * @hideconstructor
 */
export class GridDropdownColumn extends GridColumn {
    static get canEdit() { return true };

    /**
     * @ignore
     * @returns {HTMLElement}
     */
    static create() {
        return createElement('span', 'ui-drop-span', createElement('span'));
    }

    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} col 
     * @param {HTMLElement} container 
     * @param {GridItemWrapper} wrapper 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, col, container, wrapper) {
        const drop = new Dropdown({
            ...col.dropOptions,
            wrapper: container.parentElement
        });
        drop.onSelected = trigger;
        drop.onExpanded = () => {
            if (wrapper.__editing == null) {
                wrapper.__editing = {
                    [col.key]: true
                }
            } else {
                wrapper.__editing[col.key] = true;
            }
            if (typeof col.onDropExpanded === 'function') {
                col.onDropExpanded.call(col, wrapper.values, drop);
            }
        };
        drop.onCollapsed = () => {
            if (typeof col.onDropCollapsed === 'function') {
                col.onDropCollapsed.call(col, wrapper.values, drop);
            }
        };
        return drop.create();
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @returns {HTMLElement}
     */
    static getElement(element) {
        if (element.tagName === 'DIV') {
            return element.children[0].children[0];
        }
        return element.children[0];
    }

    /**
     * @private
     * @param {HTMLElement} element 
     * @returns {Dropdown}
     */
    static _getDrop(element) {
        /**
         * @type {Map<string, Dropdown>}
         */
        const dropGlobal = global[SymbolDropdown];
        if (dropGlobal == null) {
            return null;
        }
        const dropId = element.dataset.dropId;
        const drop = dropGlobal[dropId];
        if (drop == null) {
            return null;
        }
        return drop;
    }

    /**
     * @private
     * @param {GridItemWrapper} wrapper 
     * @param {GridColumnDefinition} col 
     * @returns {GridSourceItem[]}
     */
    static _getSource(wrapper, col) {
        let source;
        if (col.sourceCache !== false) {
            source = wrapper.source?.[col.key];
            if (source != null) {
                return source;
            }
        }
        source = col.source;
        if (typeof source === 'function') {
            source = source(wrapper.values);
        }
        if (col.sourceCache !== false) {
            if (wrapper.source == null) {
                wrapper.source = { [col.key]: source };
            } else {
                wrapper.source[col.key] = source;
            }
        }
        return source;
    }

    /**
     * @private
     * @param {GridSourceItem[]} source 
     * @param {HTMLElement} element 
     * @param {any} val 
     * @param {DropdownOptions} opts 
     */
    static _setValue(source, element, val, opts) {
        const data = source?.find(v => v[opts?.valueKey ?? 'value'] === val);
        if (data != null) {
            val = data[opts?.textKey ?? 'text'];
        }
        element.children[0].innerText = val;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {any} val 
     * @param {GridItemWrapper} wrapper 
     * @param {GridColumnDefinition} col 
     * @param {Grid} grid 
     */
    static setValue(element, val, wrapper, col, grid) {
        if (element.tagName !== 'DIV') {
            let source = this._getSource(wrapper, col);
            if (source instanceof Promise) {
                source.then(s => this._setValue(s, element, val, col.dropOptions));
            } else {
                this._setValue(source, element, val, col.dropOptions);
            }
            return;
        }
        const drop = this._getDrop(element);
        if (drop == null) {
            return;
        }
        const ignoreCase = col.dropRestrictCase !== true;
        if (drop.source == null || drop.source.length === 0) {
            let source = this._getSource(wrapper, col);
            if (source instanceof Promise) {
                source.then(s => {
                    drop.source = s;
                    drop.select(val, true, ignoreCase);
                })
                return;
            } else if (source != null) {
                drop.source = source;
            }
        }
        if (typeof val === 'string' && val !== '') {
            const lVal = String(val).toLowerCase();
            const item = drop.source.find(s => {
                let v = s[col.dropOptions?.valueKey ?? 'value'];
                if (ignoreCase) {
                    return String(v).toLowerCase() === lVal;
                }
                return v === val;
            });
            if (item == null) {
                let text;
                if (col.text == null && typeof col.filter === 'function') {
                    text = col.filter(wrapper.values, false, grid._var.refs.body);
                } else {
                    text = val;
                }
                drop.source.push({
                    [col.dropOptions?.textKey ?? 'text']: text,
                    [col.dropOptions?.valueKey ?? 'value']: val
                });
            }
        }
        drop.select(val, true, ignoreCase);
    }

    /**
     * @ignore
     * @param {GridSourceItem} e 
     * @param {GridColumnDefinition} col 
     * @returns {any}
     */
    static getValue(e, col) {
        return {
            value: e[col.dropOptions?.valueKey ?? 'value'],
            text: e[col.dropOptions?.textKey ?? 'text']
        };
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} name 
     */
    static setClass(element, name) {
        if (element.tagName === 'DIV') {
            element.className = `ui-drop-wrapper ${name ?? ''}`;
        } else {
            super.setClass(element, name);
        }
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} enabled 
     */
    static setEnabled(element, enabled) {
        super.setEnabled(element, enabled);
        const drop = this._getDrop(element);
        if (drop == null) {
            return;
        }
        drop.disabled = enabled === false;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {HTMLElement} container 
     */
    static leaveEdit(element, container) {
        container.parentElement.querySelectorAll('.ui-drop-box.active').forEach(e => {
            if (e != null) {
                e.classList.remove('active');
            }
        });
        const drop = this._getDrop(element);
        if (drop == null) {
            return;
        }
        if (drop?.multiSelect && typeof drop.onCollapsed === 'function') {
            drop.onCollapsed();
        }
    }

    /**
     * @ignore
     */
    static toString() { return 'GridDropdown' }
}

/**
 * 复选框列
 * @class
 * @static
 * @extends GridColumn
 * @hideconstructor
 * @ignore
 */
export class GridCheckboxColumn extends GridColumn {
    static get canEdit() { return true };

    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} col 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, col) {
        const check = createCheckbox({
            switch: col.switch,
            onchange: trigger
        });
        return check;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} val 
     */
    static setValue(element, val) {
        // element.querySelector('input').checked = val;
        element.children[0].checked = val;
    }

    /**
     * @ignore
     * @param {Event} e 
     * @returns {boolean}
     */
    static getValue(e) { return e.target.checked }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} name 
     */
    static setClass(element, name) {
        if (element.tagName === 'LABEL') {
            element.className = `ui-check-wrapper ${name ?? ''}`;
        } else {
            super.setClass(element, name);
        }
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} enabled 
     */
    static setEnabled(element, enabled) {
        super.setEnabled(element, enabled);
        // element.querySelector('input').disabled = enabled === false;
        element.children[0].disabled = enabled === false;
    }

    /**
     * @ignore
     */
    static toString() { return 'GridCheckbox' }
}

/**
 * 单选框列
 * @class
 * @static
 * @extends GridCheckboxColumn
 * @hideconstructor
 * @ignore
 */
export class GridRadioboxColumn extends GridCheckboxColumn {
    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} _col 
     * @param {number} index 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, _col, index) {
        const check = createRadiobox({
            name: `r_${index}`,
            onchange: trigger
        });
        return check;
    }

    /**
     * @ignore
     */
    static toString() { return 'GridRadiobox' }
}

/**
 * 图标列
 * @class
 * @static
 * @extends GridColumn
 * @hideconstructor
 * @ignore
 */
export class GridIconColumn extends GridColumn {
    /**
     * @ignore
     * @returns {HTMLElement}
     */
    static create() { return createElement('span', 'col-icon') }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} val 
     * @param {GridItemWrapper} wrapper 
     * @param {GridColumnDefinition} col 
     */
    static setValue(element, val, wrapper, col) {
        // let className = col.iconClassName;
        // if (typeof className === 'function') {
        //     className = className.call(col, wrapper.values);
        // }
        // if (className == null) {
        //     element.className = 'col-icon';
        // } else {
        //     element.className = `col-icon ${className}`;
        // }
        let type = col.iconType;
        if (typeof type === 'function') {
            type = type.call(col, wrapper.values);
        }
        type ??= 'fa-light';
        if (element.dataset.type !== type || element.dataset.icon !== val) {
            const icon = createIcon(type, val);
            // const layer = element.children[0];
            element.replaceChildren(icon);
            // !nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip, false, grid.element);
            element.dataset.type = type;
            element.dataset.icon = val;
        }
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} name 
     */
    static setClass(element, name) {
        element.className = `col-icon ${name ?? ''}`;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} enabled 
     */
    static setEnabled(element, enabled) {
        super.setEnabled(element, enabled);
        if (enabled === false) {
            element.classList.add('disabled');
        } else {
            element.classList.remove('disabled');
        }
    }

    /**
     * @ignore
     */
    static toString() { return 'GridIcon' }
}

/**
 * 日期选择列
 * @class
 * @static
 * @extends GridColumn
 * @hideconstructor
 */
export class GridDateColumn extends GridColumn {
    static get editing() { return true };
    static get canEdit() { return true };

    /**
     * @ignore
     * @param {Function} trigger 
     * @param {GridColumnDefinition} col 
     * @param {HTMLElement} _container 
     * @param {GridItemWrapper} wrapper 
     * @returns {HTMLElement}
     */
    static createEdit(trigger, col, _container, wrapper) {
        let enabled = col.enabled;
        if (typeof enabled === 'string') {
            enabled = wrapper.values[enabled];
        } else if (typeof enabled === 'function') {
            enabled = col.enabled(wrapper.values);
        }
        if (enabled === false) {
            return super.create();
        }
        const date = createDateInput(col.dateMin, col.dateMax);
        date.addEventListener('change', () => super._changeValue(col.key, wrapper, date.value));
        date.addEventListener('blur', trigger);
        return date;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {(string | number | Date)} val 
     * @param {GridItemWrapper} _wrapper 
     * @param {GridColumnDefinition} col 
     */
    static setValue(element, val, _wrapper, col) {
        setDateValue(element, val, col.dateDisplayFormatter);
    }

    /**
     * @ignore
     * @param {Event} e 
     * @param {GridColumnDefinition} col 
     * @returns {string}
     */
    static getValue(e, col) {
        if (e.target.tagName === 'INPUT') {
            return {
                value: getDateValue(e.target, col.dateValueFormatter),
                text: getDateValue(e.target, col.dateDisplayFormatter)
            };
        }
        return e.target.innerText;
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {string} name 
     */
    static setClass(element, name) {
        if (element.tagName === 'INPUT') {
            element.className = `ui-date-cell ${name ?? ''}`;
        } else {
            super.setClass(element, name);
        }
    }

    /**
     * @ignore
     * @param {HTMLElement} element 
     * @param {boolean} enabled 
     */
    static setEnabled(element, enabled) {
        element.disabled = enabled === false;
    }

    /**
     * 格式化日期字符串
     * @param {(string | number | Date)} date - 要格式化的日期值
     * 
     * 支持以下几种数据类型
     * * `"2024-01-26"`
     * * `"1/26/2024"`
     * * `"638418240000000000"`
     * * `new Date('2024-01-26')`
     * @param {string} [formatter] - 格式化格式，默认为 `"m/d/Y"`
     * 
     * * Y - 年，例如 `2024`
     * * y - 年的后两位，例如 `"24"`
     * * n - 月，例如 `2`
     * * m - 格式化成两位的月，例如 `"02"`
     * * j - 日，例如 `4`
     * * d - 格式化成两位的日，例如 `"04"`
     * * t - 日期当月总天数，例如 `29`
     * * L - 是否为闰年，例如 `1`
     * * G - 小时，例如 `15`
     * * g - 12 小时制的小时，例如 `3`
     * * H - 格式化成两位的小时，例如 `"15"`
     * * h - 格式化成两位的 12 小时制的小时，例如 `"03"`
     * * A - 上下午，例如 `"PM"`
     * * a - 小写的上下午，例如 `"pm"`
     * * i - 格式化成两位的分钟，例如 `"39"`
     * * s - 格式化成两位的秒，例如 `"20"`
     * * u - 格式化成六位的毫秒，例如 `"023040"`
     * * e - 时区描述字符串，例如 `"China Standard Time"`
     * * O - 时区偏移字符串，例如 `"+0800"`
     * * P - `"+08:00"` 这种格式的时区偏移字符串
     * @returns {string} 格式化为 M/d/yyyy 的日期字符串
     */
    static formatDate(date, formatter) {
        return formatDate(date, formatter);
    }

    /**
     * @ignore
     */
    static toString() { return 'GridDate' }
}