/**
 * Render端DOM相关接口
 */
import { getUid, emptyFn, throwError } from './util/index';

/**
 * 查询节点信息的对象
 *
 * @class SelectorQuery
 */
class SelectorQuery {
    constructor(self = Service.location.currentPage) {
        this._component = '';
        this._hasInService = false;
        this._defaultComponent = self.$root.$page ? self.$root.$page.id : self.$root.$options.$page.id;
        this._queue = [];
        this._queueCb = [];
    }

    /**
     * 执行所有的请求。请求结果按请求次序构成数组，在callback的第一个参数中返回。
     *
     * @param {Function} cb 回调函数
     * @memberof SelectorQuery
     */
    exec(cb) {
        const callbackId = getUid();
        const message = {
            type: 'dom.query',
            queue: this._queue,
            callbackId: callbackId
        };
        const callback = typeof cb === 'function' ? cb : emptyFn;
        const fireCallback = (res) => {
            let _pageId = global.__biliCacheData.getCurrentPageId();
            // 特殊处理canvas，进行桥接。
            if (res && Array.isArray(res) && res.length > 0) {
                let _temp = res.filter(x=> x && x['getCanvasInstance']);
                let _isReadyByAll = true;
                // 已经存在canvas了，需要替换处理
                if (_temp.length > 0) {
                    _temp.forEach(_x => {
                        let _readyForCanvas = (global.canvas &&
                            global.canvas[_pageId] &&
                            global.canvas[_pageId][_x["getCanvasInstance"]] &&
                            global.canvas[_pageId][_x["getCanvasInstance"]].status === 'ready');
                        _isReadyByAll = _isReadyByAll && _readyForCanvas
                    });

                    // console.log('>>> test look:', res, this._queueCb, _isReadyByAll)
                    if (_isReadyByAll) {
                        res.forEach(x => {
                            if (x["getCanvasInstance"]) {
                                if (bl.isCanvasSupportedSync()) {
                                    x['node'] = global.canvas[_pageId][x["getCanvasInstance"]].canvas;
                                    // 特殊兼容历史版本...
                                    global.canvas[_pageId][x["getCanvasInstance"]].canvas.createImage = global.__biliCacheData.canvas.Image;
                                    global.canvas[_pageId][x["getCanvasInstance"]].canvas.createImageData = global.__biliCacheData.canvas.ImageData;
                                } else {
                                    x['node'] = null;
                                }
                                // 删除标记
                                delete x["getCanvasInstance"];
                            }
                        });
                        this._queueCb.forEach((item, index) => {
                            if (item) {
                                item(res[index]);
                            }
                        });
                        callback(res);
                        // 删除数据
                        if (Service.callbacks[callbackId]) delete Service.callbacks[callbackId];
                    } else {
                        // console.log('>>> 走了缓存...')
                        // 如果不存在则缓存，等待canvas ready。
                        global.__biliComponentData.canvas.waitingCanvas.push({
                            callback,
                            params: res,
                            queueCb: this._queueCb,
                            callbackId
                        })
                    }
                } else {
                    this._queueCb.forEach((item, index) => {
                        if (item) {
                            item(res[index]);
                        }
                    });
                    callback(res);
                    // 删除数据
                    if (Service.callbacks[callbackId]) delete Service.callbacks[callbackId];
                }
            } else {
                // 如果不是数组
                this._queueCb.forEach((item, index) => {
                    if (item) {
                        item(res[index]);
                    }
                });
                callback(res);
                // 删除数据
                if (Service.callbacks[callbackId]) delete Service.callbacks[callbackId];
            }
        };

        Service.postMessage(JSON.stringify(message), this._defaultComponent);
        Service.callbacks[callbackId] = fireCallback;
    }

    /**
     * 将选择器的选取范围更改为自定义组件 component 内。（初始时，选择器仅选取页面范围的节点，不会选取任何自定义组件中的节点）。
     *
     * @param {VueComponent} component 组件实例
     * @returns {SelectorQuery}
     * @memberof SelectorQuery
     */
    in (component) {
        if (component instanceof Vue) {
            this._component = component._bili_id || component._uid;
        }
        return this;
    }

    /**
     * 在当前页面下选择第一个匹配选择器 selector 的节点。返回一个 NodesRef 对象实例，可以用于获取节点信息。
     *
     * @param {String} selector 选择器
     * @returns {NodeRef}
     * @memberof SelectorQuery
     */
    select(selector) {
        return new NodeRef(this, selector.replace(/>>>/g, ' '), true);
    }

    /**
     * 在当前页面下选择匹配选择器 selector 的所有节点。
     *
     * @param {String} selector 选择器
     * @returns {NodeRef}
     * @memberof SelectorQuery
     */
    selectAll(selector) {
        return new NodeRef(this, selector.replace(/>>>/g, ' '), false);
    }

    /**
     * 选择显示区域。可用于获取显示区域的尺寸、滚动位置等信息。
     *
     * @returns {NodeRef}
     * @memberof SelectorQuery
     */
    selectViewport() {
        return new NodeRef(this, null, true);
    }

    /**
     * 向实例的队列中塞入query需要的信息
     *
     * @param {VueComponent} component 对应组件
     * @param {Object} fields 返回值中需要属性的k-v对
     * @param {String} selector 选择器
     * @param {Boolean} single 是否选择单个节点
     * @param {Function} [callback = null] 注册的回调
     * @memberof SelectorQuery
     */
    _push(component, fields, selector, single, callback = null, hasInService = false) {
        const resultField = Object.assign({}, fields);
        if (!Array.isArray(resultField.properties)) {
            resultField.properties = null;
        }

        if (!Array.isArray(resultField.computedStyle)) {
            resultField.computedStyle = null;
        }

        this._queue.push({
            component,
            fields: resultField,
            selector,
            single,
            hasInService,
        });
        this._queueCb.push(callback);
    }
}

const BOUNDING_FIELDS = {
    id: true,
    dataset: true,
    rect: true,
    size: true
};
const SCROLL_FIELDS = {
    id: true,
    dataset: true,
    scrollOffset: true
};

/**
 * 用于获取 DOM 节点信息的对象
 *
 * @class NodeRef
 */
class NodeRef {
    constructor(selectorQuery, selector, single) {
        this._selectorQuery = selectorQuery;
        this._selector = selector;
        this._component = selectorQuery._component;
        this._single = single;
        this._hasInService = false;

        // Service是否存在该组件，如果有需要render层等待渲染完毕。
        if (Service.components[this._component]) this._hasInService = true;
    }

    /**
     * 获取节点的相关信息。需要获取的字段在fields中指定。返回值是 nodesRef 对应的 selectorQuery
     *
     * @param {Object} fields
     * @returns {SelectorQuery}
     * @memberof NodeRef
     */
    fields(fields, callback) {
        this._selectorQuery._push(
            this._component,
            fields,
            this._selector,
            this._single,
            typeof callback === 'function' ? callback : null,
            this._hasInService
        );

        return this._selectorQuery;
    }

    node(callback) {
        this._selectorQuery._push(
            this._component,
            {
                node: true
            },
            this._selector,
            this._single,
            typeof callback === 'function' ? callback : null,
            this._hasInService
        );

        return this._selectorQuery;
    }

    /**
     * 添加节点的布局位置的查询请求。相对于显示区域，以像素为单位。其功能类似于 DOM 的 getBoundingClientRect。返回 NodesRef 对应的 SelectorQuery。
     *
     * @param {Function} callback
     * @returns {SelectorQuery}
     * @memberof NodeRef
     */
    boundingClientRect(callback) {
        this._selectorQuery._push(
            this._component,
            BOUNDING_FIELDS,
            this._selector,
            this._single,
            typeof callback === 'function' ? callback : null,
            this._hasInService
        );

        return this._selectorQuery;
    }

    /**
     * 添加节点的滚动位置查询请求。以像素为单位。节点必须是 scroll-view 或者 viewport，返回 NodesRef 对应的 SelectorQuery。
     *
     * @param {Object} callback
     * @returns {SelectorQuery}
     * @memberof NodeRef
     */
    scrollOffset(callback) {
        this._selectorQuery._push(
            this._component,
            SCROLL_FIELDS,
            this._selector,
            this._single,
            typeof callback === 'function' ? callback : null,
            this._hasInService
        );

        return this._selectorQuery;
    }
}

/**
 * IntersectionObserver 对象，用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。
 *
 * @class IntersectionObserver
 */
class IntersectionObserver {
    constructor(self, options) {
        if (!self) self = Service.location.currentPage;
        this._component = self.$root.$page ? self.$root.$page.id : self.$root.$options.$page.id;
        this._disconnected = false;
        this._observerId = getUid();
        this._options = options;
        this._relativeInfo = [];
    }

    /**
     * 停止监听。回调函数将不再触发
     *
     * @memberof IntersectionObserver
     */
    disconnect() {
        this._disconnected = true;

        const message = {
            type: 'dom.observe',
            observerId: this._observerId,
            disconnect: true,
        };

        Service.postMessage(JSON.stringify(message), this._component);
    }

    /**
     * 指定目标节点并开始监听相交状态变化情况
     *
     * @param {String} selector 选择器
     * @param {Object} callback 回调
     * @memberof IntersectionObserver
     */
    observe(selector, callback) {
        if (Service.callbacks[this._observerId]) {
            throwError('"observe" call can be only called once in IntersectionObserver');
        }

        Service.callbacks[this._observerId] = callback.bind(this);

        const message = {
            type: 'dom.observe',
            target: selector,
            observerId: this._observerId,
            relativeInfo: this._relativeInfo,
            options: this._options
        };

        Service.postMessage(JSON.stringify(message), this._component);
    }

    /**
     * 使用选择器指定一个节点，作为参照区域之一。
     *
     * @param {*} selector 选择器
     * @param {*} [margins = { left: 0, right: 0, top: 0, bottom: 0 }] 用来扩展（或收缩）参照节点布局区域的边界
     * @memberof IntersectionObserver
     */
    relativeTo(selector, {
        left = 0,
        right = 0,
        top = 0,
        bottom = 0
    } = {}) {
        this._relativeInfo.push({
            selector,
            margins: {
                left,
                right,
                top,
                bottom
            }
        });

        return this;
    }

    /**
     * 指定页面显示区域作为参照区域之一
     *
     * @param {*} [margins = { left: 0, right: 0, top: 0, bottom: 0 }] 用来扩展（或收缩）参照节点布局区域的边界
     * @memberof IntersectionObserver
     */
    relativeToViewport({
        left = 0,
        right = 0,
        top = 0,
        bottom = 0
    } = {}) {
        this._relativeInfo.push({
            selector: null,
            margins: {
                left,
                right,
                top,
                bottom
            }
        });

        return this;
    }
}

export default {
    /**
     * 返回一个 SelectorQuery 对象实例。
     *
     * @param {Object} self 组件实例
     * @returns {SelectorQuery}
     */
    createSelectorQuery(self) {
        return new SelectorQuery(self);
    },
    /**
     * 创建并返回一个 IntersectionObserver 对象实例。
     *
     * @param {Object} self 组件实例
     * @param {Object} options 选项
     * @param {Number[]} [options.thresholds = [0]] 一个数值数组，包含所有阈值。
     * @param {Number} [options.initialRatio = 0] 初始的相交比例
     * @param {Boolean} [options.observeAll = false] 是否同时观测多个目标节点
     * @returns {IntersectionObserver}
     */
    createIntersectionObserver(self, _options = {
        thresholds:[0],
        initialRatio:0,
        observeAll:false
    }) {
        if (!self) self = Service.location.currentPage;
        if (!self.$root) {
            _options = Object.assign({}, self);
            self = Service.location.currentPage;
        }
        const options = {
            thresholds: Array.isArray(_options.thresholds) ? _options.thresholds : [0],
            initialRatio: _options.initialRatio || 0,
            observeAll: !!_options.observeAll
        };
        return new IntersectionObserver(self, options);
    }
};
