import { createElement } from "../functions";

/**
 * 创建或转换日期选择框
 * @param {string} [min] - 最小可选日期
 * @param {string} [max] - 最大可选日期
 * @param {HTMLInputElement | string} [element] - 转换该元素为日期选择框
 * @returns {HTMLInputElement} 返回创建或转换的日期选择框
 */
export function createDateInput(min, max, element) {
    let date;
    if (typeof element === 'string') {
        element = document.querySelector(element);
    }
    if (element instanceof HTMLInputElement) {
        date = element;
        date.classList.add('ui-date-cell');
    } else {
        date = createElement('input', 'ui-date-cell');
    }
    date.required = true;
    date.type = 'date';
    if (min != null) {
        date.min = min;
    }
    if (max != null) {
        date.max = max;
    }
    return date;
}

/**
 * 将日期转换为 `yyyy-MM-dd` 格式的字符串
 * @param {Date} dt 要转换的日期值
 * @returns 返回 `yyyy-MM-dd` 格式的字符串
 */
export function toDateValue(dt) {
    if (isNaN(dt)) {
        return '';
    }
    const month = String(dt.getMonth() + 1).padStart(2, '0');
    const date = String(dt.getDate()).padStart(2, '0');
    return `${dt.getFullYear()}-${month}-${date}`;
}

function resolveDate(s) {
    if (s instanceof Date) {
        return s;
    }
    const ticks = Number(s);
    if (!isNaN(ticks) && ticks > 0) {
        return new Date((ticks - 621355968e9) / 1e4);
    }
    return new Date(s);
}

/**
 * 格式化日期为 M/d/yyyy 格式的字符串
 * @param {Date | number | string} date - 需要格式化的日期值,支持的格式如下:
 * 
 * * `"2024-01-26"`
 * * `"2024-01-26T00:00:00"`
 * * `"1/26/2024"`
 * * `"638418240000000000"`
 * * `new Date('2024-01-26')`
 * @returns {string} 返回格式化后的日期字符串
 */
export function formatDate(date) {
    date = resolveDate(date);
    if (date instanceof Date && !isNaN(date)) {
        return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
    }
    return '';
}

/**
 * 设置显示日期
 * @param {HTMLElement} element - 要设置显示日期的元素
 * @param {Date | number | string} val - 日期值,支持格式参见 {@linkcode formatDate}
 */
export function setDateValue(element, val) {
    if (element.tagName === 'INPUT') {
        if (val === '') {
            element.value = '';
        } else if (isNaN(val)) {
            if (/^\d{4}-\d{2}-\d{2}/.test(val)) {
                element.value = String(val).substring(0, 10);
            } else if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) {
                element.value = toDateValue(new Date(val));
            } else {
                element.value = '';
            }
        } else {
            if (!(val instanceof Date)) {
                val = new Date((val - 621355968e9) / 1e4);
            }
            element.value = toDateValue(val);
        }
    } else {
        element.innerText = formatDate(val);
    }
}

/**
 * 自定义日期格式化回调函数
 * @callback DateFormatterCallback
 * @param {Date} date - 日期值
 * @returns {any} 返回格式化后的结果
 */

/**
 * 从日期选择框获取日期值
 * @param {HTMLInputElement} element - 要获取的日期选择框
 * @param {DateFormatterCallback} [formatter] - 自定义格式化函数,传入参数为 `Date` 类型
 * @returns {string | any} 默认返回日期 `ticks` 的字符串
 */
export function getDateValue(element, formatter) {
    const date = element?.valueAsDate;
    if (date instanceof Date && !isNaN(date)) {
        const year = date.getFullYear();
        if (year < 1900 || year > 9999) {
            return '';
        }
        if (typeof formatter === 'function') {
            return formatter(date);
        }
        return String(date.getTime() * 1e4 + 621355968e9);
    }
    return '';
}

/**
 * 日期选择框类
 * @class
 */
export class DateSelector {
    _var = {
        options: null
    };

    /**
     * 日期发生变化时触发的事件
     * @event
     * @param {Date} date - 修改后的日期值
     * @this DateSelector
     */
    onDateChanged;

    /**
     * 日期选择框构造函数
     * @constructor
     * @param {object} [opts] - 日期选择框初始化参数
     * @param {boolean} [opts.enabled] - 是否可用
     * @param {string} [opts.minDate] - 最小可选择日期
     * @param {string} [opts.maxDate] - 最大可选择日期
     * @param {DateFormatterCallback} [opts.valueFormatter] - 自定义格式化函数
     * @example <caption>创建一个 DateSelector 并插入到元素中</caption>
     *  <div id="dateFrom"></div>
     * @example
     *  // 构造参数
     *  const opts = {
     *      minDate: '2020-01-01',
     *      maxDate: '2024-02-01',
     *      valueFormatter: (date) => {
     *          return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;  // 2024/1/31
     *      }
     *  };
     *  const dateSelectorFrom = new DateSelector(opts);
     *  document.querySelector('#dateFrom').appendChild(dateSelectorFrom.create());
     * @example <caption>将一个元素转化为 DateSelector</caption>
     *  <input id="dateTo"></div>
     * @example
     *  const dateSelectorTo = new DateSelector();
     *  dateSelectorTo.create('#dateTo');
     */
    constructor(opts) {
        this._var.options = opts ?? {};
    }

    /**
     * 创建或转换日期选择框元素
     * @param {HTMLInputElement | string} [element] - 转换该元素为日期选择框
     * @returns {HTMLInputElement} 返回创建或转换的日期选择元素
     */
    create(element) {
        const opts = this._var.options;
        const el = createDateInput(opts.minDate, opts.maxDate, element);
        if (element == null) {
            el.disabled = opts.enabled === false;
        }
        el.addEventListener('blur', e => {
            const date = this._getDate(e.target.valueAsDate);
            if (date == null) {
                e.target.value = '';
            }
            if (typeof this.onDateChanged === 'function') {
                this.onDateChanged(date);
            }
        });
        this._var.el = el;
        return el;
    }

    /**
     * 获取日期选择框元素
     * @readonly
     * @type {HTMLInputElement}
     */
    get element() { return this._var.el }

    /**
     * 获取或设置日期选择框是否启用
     * @type {boolean}
     */
    get enabled() { return !this._var.el.disabled }
    set enabled(flag) {
        this._var.el.disabled = flag === false;
    }

    /**
     * 获取格式化的日期值,或设置日期值,支持的格式参见 {@linkcode formatDate}
     * @type {string | any}
     */
    get value() { return this._getDate(this._var.el.valueAsDate) }
    set value(val) {
        setDateValue(this._var.el, val);
    }

    /**
     * 获取或设置最小可选择日期
     * @type {string}
     */
    get minDate() { return this._var.el.min }
    set minDate(date) {
        this._var.el.min = date;
        this._var.options.minDate = date;
    }

    /**
     * 获取或设置最大可选择日期
     * @type {string}
     */
    get maxDate() { return this._var.el.max }
    set maxDate(date) {
        this._var.el.max = date;
        this._var.options.maxDate = date;
    }

    _getDate(date) {
        if (date instanceof Date && !isNaN(date)) {
            const year = date.getFullYear();
            if (year < 1900 || year > 9999) {
                return null;
            }
            if (typeof this._var.options.valueFormatter === 'function') {
                return this._var.options.valueFormatter(date);
            }
            return date;
        }
        return null;
    }

    /**
     * 把父容器下所有匹配 `input[data-type="date"]` 的元素修改为统一的日期选择框<br/><br/>
     * 解析的属性为 `id`, `class`, `data-min`, `data-max`, `disabled`
     * @static
     * @param {HTMLElement} [dom=document.body] 父元素
     * @param {Function} [trigger] 日期设置事件触发函数
     * @param {Date} trigger.{this} - 上下文为触发设置日期的 `DateSelector` 实例
     * @param {Date} trigger.date - 修改后的日期值
     * @example <caption>HTML</caption>
     *  <input id="dateFrom" data-type="date" data-min="1980-01-01"/>
     * @example <caption>解析父容器</caption>
     *  // 解析 document.body 下所有符合条件的元素,转换为日期选择框,第二个参数可选
     *  DateSelector.resolve(document.body, function (date) {
     *      console.log(`element(#${this.element.id}), date changed to: ${formatDate(date)}`);
     *      // 当日期选择改变时,控制台将会输出:element(#dateFrom), date changed to: 1/30/2024
     *  });
     *  @example <caption>其他地方调用时</caption>
     *  const value = document.querySelector('#dateFrom').value;
     *  console.log(`dateFrom.value = '${value}', formatted: '${formatDate(value)}'`);
     *  // 控制台会输出:dateFrom.value = '2024-01-30', formatted: '1/30/2024'
     */
    static resolve(dom = document.body, trigger) {
        const dates = dom.querySelectorAll('input[data-type="date"]');
        for (let dat of dates) {
            const val = dat.value;
            const dateSelector = new DateSelector({
                minDate: dat.getAttribute('data-min'),
                maxDate: dat.getAttribute('data-max')
            });
            if (typeof trigger === 'function') {
                dateSelector.onDateChanged = date => trigger.call(dateSelector, date);
            }
            dat.removeAttribute('data-type');
            dat.removeAttribute('data-min');
            dat.removeAttribute('data-max');
            dateSelector.create(dat);
            dateSelector.value = val;
        }
    }
}