import './global'
import Page from './page';
import API from './bl';
import { showSystemLog, getUid, serialize } from './nativeApi/util';
import './lib/selectComponent';
import { Audio } from './nativeApi/audio';
import Media from './nativeApi/media';
import Network from './nativeApi/network';
import Interface from './nativeApi/interface';
import Private, {getAccountLoginCallback, getAccountLogoffCallback} from './nativeApi/private';
import * as eventHandlers from './nativeApi/util/eventHandlers';
import parseQuery from './lib/parse-query';
import compareVersion from './lib/compareVersion';
import makeShareQuery from './lib/makeShareQuery';
import appJson from '%/app.json';
import {is} from "./nativeApi/util/commonFn";
import './channel'

// Vue
Vue.mixin({
    beforeDestroy () {
        if (!Service.version) {
            return;
        }
        delete this.$root.$page.components[this._bili_id || this._uid];
    }
});


/* global Service, bl, APP_JSON */
// 安卓专用
if (typeof window === 'object') {
    Vue.config.errorHandler = function (err) {
        Service.callNative('inner.onError', JSON.stringify({
            message: err.message,
            stack: err.stack
        }), getUid());
    };
}

function getPropertyBySafe(obj, path) {
    const _path = path ? path.split('.') : [];
    return _path.reduce(function(obj, currPath){
        if (obj !== null && obj !== undefined) {
            return obj[currPath];
        } else {
            return obj;
        }
    }, obj);
}
Vue.prototype._mkarray = function(node, section, value) {
    const keys = section.split(/\[(\d+)\]/).filter(i => i !== '');
    const lastIndex = keys.pop();
    keys.forEach(key => {
        if (!Array.isArray(node[key])) {
            Vue.set(node, key, []);
        }
        node = node[key];
    });
    if (value === undefined) {
        Object.prototype.toString.call(node[lastIndex]) === '[object Object]' ? (value = node[lastIndex]) : (value = {});
    }
    Vue.set(node, lastIndex, value);
    return node[lastIndex];
};
Vue.prototype.setData = function(data) {
    if (typeof data === 'object') {
        for (const key in data) {
            if (data.hasOwnProperty(key)) {
                const value = data[key];

                const pathArray = key.split('.');
                const lastKey = pathArray.pop();

                let node = this.$data;
                let shadowNode = this.$data.data;
                for (let i = 0; i < pathArray.length; i++) {
                    const section = pathArray[i];
                    if (/\[\d+\]/.test(section)) {
                        shadowNode = this._mkarray(shadowNode, section);
                        node = this._mkarray(node, section);
                    } else {
                        if (typeof node[section] !== 'object' || node[section] == null) {
                            shadowNode[section] = {};
                            Vue.set(node, section, {});
                        }
                        shadowNode = shadowNode[section];
                        node = node[section];
                    }
                }
                if (/\[\d+\]/.test(lastKey)) {
                    this._mkarray(shadowNode, lastKey, value);
                    this._mkarray(node, lastKey, value);
                } else {
                    if (!pathArray.length && node[key] && typeof node[key] === 'object' && node[key] === value) {
                        Service.postMessage(JSON.stringify({
                            data: [{
                                path: key,
                                value: node[key],
                                id: this._bili_id || this._uid
                            }]
                        }), this.$root.$page.id);
                    }
                    shadowNode[lastKey] = value;
                    Vue.set(node, lastKey, value);
                }
            }
        }
    }
};
Vue.prototype.triggerEvent = function(eventName, eventPayload, { bubbles = false } = {}) {
    if (bubbles) {
        let parent = this;
        while (parent) {
            parent.$emit(eventName, {
                detail: eventPayload,
            });
            parent = parent.$parent;
        }
        // const message = {
        //     type: 'dom.trigger',
        //     id: this._uid,
        //     name: eventName,
        //     payload: eventPayload,
        //     stop: !composed
        // };
        // const pageId = this.$root.$page ? this.$root.$page.id : this.$options.$page.id;
        // Service.postMessage(JSON.stringify(message), pageId);
    } else {
        this.$emit(eventName, {
            detail: eventPayload,
        });
    }
};
Vue.prototype.$methodInService = function(methodName, args) {
    if (typeof this[methodName] === 'function') {
        this[methodName].apply(this, args);
    }
};
Vue.prototype.createSelectorQuery = function() {
    return bl.createSelectorQuery(this).in(this);
};
Vue.prototype.createIntersectionObserver = function(options) {
    return bl.createIntersectionObserver(this, options);
};

bl.appInstance = null;
bl.getApp = () => {
    return bl.appInstance;
};

for (const key in API) {
    bl[key] = API[key];
}

const systemInfo = bl.getSystemInfoSync();
const isiOS = systemInfo.platform === 'ios';

if (isiOS) {
    const typeArr = ['log', 'debug', 'info', 'warn', 'error', 'assert', 'trace'];
    const notSupportType = ['debug', 'assert', 'trace'];
    const copyConsole = {};

    typeArr.forEach(handler => {
        copyConsole[handler] = console[handler];

        console[handler] = function() {
            const args = Array.prototype.slice.apply(arguments);

            for (let index in args) {
                let value = args[index];
                let type = Object.prototype.toString.call(value).slice(8, -1).toLocaleLowerCase();

                if (type === 'undefined') {
                    value = 'undefined';
                } else if (typeof value === 'object' || typeof value === 'function') {
                    if (type !== 'object' && type !== 'array' && type !== 'null') {
                        value = value.toString();
                    }
                }

                let newHandler = '';
                if (notSupportType.indexOf(handler) > -1) {
                    newHandler = 'log';
                }

                Service.postMessage(JSON.stringify({
                    type: 'debug',
                    event: 'vconsole',
                    handler: newHandler || handler,
                    value: serialize(value)
                }), -2);
            }

            // ios9中 console的 this 指向必须是console原生
            copyConsole[handler].apply(console, args);
        };
    });
}

// 处理安卓独有逻辑
else {
    if (typeof WebAssembly !== 'undefined' && compareVersion(systemInfo.SDKVersion, '3.2.0') >= 0) {
        let wid = 0;
        const compile = WebAssembly.compile;
        const compileStreaming = WebAssembly.compileStreaming;
        const instantiate = WebAssembly.instantiate;
        const instantiateStreaming = WebAssembly.instantiateStreaming;

        WebAssembly.compile = function () {
            let args = arguments;
            let id = wid++;

            return new Promise(resolve => {
                Service.callNative('beginWebAssemblyCompile', JSON.stringify({ id }));
                resolve();
            })
                .then(() => compile.apply(WebAssembly, args))
                .then(res => {
                    Service.callNative('endWebAssemblyCompile', JSON.stringify({ id }));
                    return res;
                });
        };

        WebAssembly.compileStreaming = function () {
            let args = arguments;
            let id = wid++;

            return new Promise(resolve => {
                Service.callNative('beginWebAssemblyCompile', JSON.stringify({ id }));
                resolve();
            })
                .then(() => compileStreaming.apply(WebAssembly, args))
                .then(res => {
                    Service.callNative('endWebAssemblyCompile', JSON.stringify({ id }));
                    return res;
                });
        };

        WebAssembly.instantiate = function () {
            let args = arguments;
            let id = wid++;

            return new Promise(resolve => {
                Service.callNative('beginWebAssemblyCompile', JSON.stringify({ id }));
                resolve();
            })
                .then(() => instantiate.apply(WebAssembly, args))
                .then(res => {
                    Service.callNative('endWebAssemblyCompile', JSON.stringify({ id }));
                    return res;
                });
        };

        WebAssembly.instantiateStreaming = function () {
            let args = arguments;
            let id = wid++;

            return new Promise(resolve => {
                Service.callNative('beginWebAssemblyCompile', JSON.stringify({ id }));
                resolve();
            })
                .then(() => instantiateStreaming.apply(WebAssembly, args))
                .then(res => {
                    Service.callNative('endWebAssemblyCompile', JSON.stringify({ id }));
                    return res;
                });
        };
    }
}


const networkStatusChangeHandlers = [];
const handleNetworkStatusChange = result => {
    networkStatusChangeHandlers.forEach(cb => {
        cb(result);
    });
};

/**
 * 监听网络状态变化事件
 *
 * @param {Function} callback 网络状态变化事件的回调函数
 */
bl.onNetworkStatusChange = callback => {
    if (typeof callback === 'function') {
        networkStatusChangeHandlers.push(callback);
    }
};

/**
 * 获取当前页面路径
 */
bl.getCurrentPage = () => {
    return Service.location.href;
};

/**
 * 获取当前页面的pageId
 *  1. 初始化完成直接从当前实例化中取
 *  2. 否则说明正在初始化，从缓存中取
 *      compnents > 缓存
 */
global.__biliCacheData.getCurrentPageId = function () {
    let _pageId = '';
    if (global.__biliCacheData.pageIdStatus === 'beforeVue') {
        _pageId = global.__biliCacheData.currentPageId;
    } else {
        _pageId = Service.location.currentPage.$root.$page.id;
    }
    return _pageId;
};

/**
 * 其实没啥用，配合微信做一下，跟微信不一致，只有当前的页面栈顶
 */
bl.getCurrentPages = () => {
    return [Service.location.currentPage];
};

showSystemLog && console.log('bl global API injected');
showSystemLog && console.log(bl);

console.log('[APP launch]', global.$buildInfo);

Service.__specialStorage = {
    isSpecial: systemInfo.SDKVersion === '1.3.2' && isiOS,
    callbackStack: [],
    done: false,
};

bl.getStorage({
    key: '__bugfix',
    complete() {
        Service.__specialStorage.done = true;
        while (Service.__specialStorage.callbackStack.length) {
            const cbObj = Service.__specialStorage.callbackStack.shift();
            cbObj.cb(...cbObj.params);
        }
    },
});

let appJSON = APP_JSON;

/**
 * NA过场动画需求
 *  提供给端上调用，然后将用户id获取，render层获取
 * */
Service.onGetTransitionPopTarget = function(pageId, token) {
    try {
        const component = Service.location.currentPage;
        const handlerFn = component.$options.onGetTransitionPopTarget;
        if (handlerFn && typeof handlerFn === 'function') {
            const _result = handlerFn.apply(component);
            // 有正常返回值
            if (typeof _result === 'string') {
                Service.postMessage(JSON.stringify({
                    type: 'fe-framework',
                    event: 'transitionPop',
                    data: {
                        token,
                        htmlId: _result
                    }
                }), pageId);
            }
        }
    } catch (e) {
        Service.onError(`'onGetTransitionPopTarget'：${e.message}`);
    }
};
Service.location = {};
Service.location.href = '';
Service.location.currentPage = null;
Service.handleMessage = function(message, pageId) {
    showSystemLog && console.log('Service.handleMessage', message, pageId);
    try {
        if(!message) {
            console.warn('message is null')
            return;
        }
        if (message.type === 'lifecycle') {
            const event = message.event;
            if (Service && Service.__specialStorage && Service.__specialStorage.isSpecial && !Service.__specialStorage.done) {
                if (event === 'onLoad') {
                    Service.__specialStorage.callbackStack.push({
                        cb(pageId, path, message) {
                            Page.pages[pageId] = new Page(pageId, path);
                            Page.pages[pageId].handleMessage(message);
                        },
                        params: [pageId, message.path, message],
                    });
                } else if (event === 'onShow') {
                    Service.__specialStorage.callbackStack.push({
                        cb(message) {
                            if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                                Page.pages[pageId].handleMessage(message);
                            } else {
                                console.warn('pageId is null')
                            }
                        },
                        params: [message],
                    });
                } else if (event === 'onUnload') {
                    if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                        Page.pages[pageId].handleMessage(message);
                    } else {
                        console.warn('pageId is null')
                    }
                    delete Page.pages[pageId].root.$vmMap[pageId];
                    delete Page.pages[pageId].root.$vmCount[pageId];
                    delete Page.pages[pageId];
                } else {
                    if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                        Page.pages[pageId].handleMessage(message);
                    } else {
                        console.warn('pageId is null')
                    }
                }
            } else {
                if (event === 'onLoad') {
                    // canvas打标记，意味着新页面过来
                    if (!global.canvas) global.canvas = {};
                    showSystemLog && console.log('Service.handleMessage onLoad');
                    Page.pages[pageId] = new Page(pageId, message.path);
                    Page.pages[pageId].handleMessage(message);

                    // ios 549,550有一个bug，外部小程序没有导航栏时胶囊按钮会消失，需要调用bl.showLoading和bl.hideLoading来显示胶囊
                    if (systemInfo.platform === 'ios' && (systemInfo.SDKVersion === '2.6.0' || systemInfo.SDKVersion === '2.7.0')) {

                        let hasNavigationBar = true;
                        if(appJson && appJson.pages) {
                            appJSON.pages.some(page => {
                                if (page && page.path === message.path) {
                                    hasNavigationBar = typeof page.hasNavigationBar !== 'undefined' ? page.hasNavigationBar : (appJSON.window && appJSON.window.hasNavigationBar || false);
                                }
                            });
                        }
                        if (!hasNavigationBar) {
                            bl.showLoading({
                                title: ''
                            });
                            bl.hideLoading();
                        }
                    }
                } else if (event === 'onUnload') {
                    showSystemLog && console.log('Service.handleMessage onUnload');
                    if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                        Page.pages[pageId].handleMessage(message);
                    } else {
                        console.warn('pageId is null')
                    }
                    // 判断兜底，存在就销毁
                    if (Page && Page.pages && Page.pages[pageId] && Page.pages[pageId].pageVm) {
                        // 触发service层页面销毁
                        Page.pages[pageId].pageVm.$destroy();
                    }
                    Vue.nextTick(()=>{
                        // 清除全局缓存的canvas，防止内存泄漏
                        if (global.canvas && global.canvas[pageId]) {
                            delete global.canvas[pageId];
                        }
                        delete Page.pages[pageId].root.$vmMap[pageId];
                        delete Page.pages[pageId].root.$vmCount[pageId];
                        delete Page.pages[pageId];
                    })

                } else {
                    if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                        Page.pages[pageId].handleMessage(message);
                    } else {
                        console.warn('pageId is null')
                    }
                }
            }
        } else if (message.type === 'render-loaded') {
            if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                Page.pages[pageId].handleMessage({
                    type: 'lifecycle',
                    event: 'onReady',
                });
            } else {
                console.warn('pageId is null')
            }
        } else if (message.type === 'system') {
            switch (message.event) {
            case 'onNetworkStatusChange':
                handleNetworkStatusChange(message.data);
                break;
            case 'onMemoryWarning':
                if(eventHandlers && eventHandlers.memoryWarningHandlers) {
                    eventHandlers.memoryWarningHandlers.forEach(callback =>
                        callback(
                            systemInfo.platform === 'ios' ? {} : {
                                level: message.level
                            })
                    );
                }
                break;
                // android keyboard
            case 'onDeviceMotionChange':
                if(eventHandlers && eventHandlers.deviceMotionChangeHandlers && eventHandlers.deviceMotionChangeHandlers[message.data.pageId]) {
                    eventHandlers.deviceMotionChangeHandlers[message.data.pageId].forEach(callback => {
                        callback(message.data);
                    });
                }
                break;
            case 'onAccelerometerChange':
                if(eventHandlers && eventHandlers.accelerometerChangeHandlers && eventHandlers.accelerometerChangeHandlers[message.data.pageId]) {
                    eventHandlers.accelerometerChangeHandlers[message.data.pageId].forEach(callback => {
                        // 安卓 N6.58版本，确定跟微信不一致，需要前端兜底。
                        const {x, y, z} = message.data;
                        callback({
                            x: isiOS? x : x / 10 ,
                            y: isiOS? y : y / 10 ,
                            z: isiOS? z : z / 10
                        });
                    });
                }
                break;
            case 'onCompassChange':
                if(eventHandlers && eventHandlers.compassChangeHandlers && eventHandlers.compassChangeHandlers[message.data.pageId]) {
                    eventHandlers.compassChangeHandlers[message.data.pageId].forEach(callback => {
                        callback(message.data);
                    });
                }
                break;
            case 'onGyroscopeChange':
                if(global.__biliCacheData.eventHandlers.gyroscopeChangeHandlers[message.data.pageId]) {
                    global.__biliCacheData.eventHandlers.gyroscopeChangeHandlers[message.data.pageId].forEach(callback => {
                        callback(message.data);
                    });
                }
                break;
            case 'onKeyboardHeightChange':
                if(eventHandlers && eventHandlers.keyboardHandlers) {
                    eventHandlers.keyboardHandlers.forEach(callback => {
                        callback(message.data);
                    });
                }
                break;
            case 'capture':
                if(global.__biliCacheData.eventHandlers.captureListeningHandlers) {
                    global.__biliCacheData.eventHandlers.captureListeningHandlers.forEach(callback => {
                        callback(message.data);
                    });
                }
                break;
            case 'onThemeChange':
                if(appJson && appJson.darkmode) {
                    const systemHandlers = eventHandlers.systemHandlers || {};
                    if(systemHandlers.onThemeChange) {
                        systemHandlers.onThemeChange.forEach(handler => {
                            handler(message.data);
                        });
                    }
                    if(systemHandlers.onWidgetThemeChange) {
                        systemHandlers.onWidgetThemeChange.forEach(handler => {
                            const {theme} = message.data;
                            const _result = Object.assign({}, message.data, {colorScheme: theme});
                            handler(_result);
                        });
                    }
                }
                break;
            default:
                if (message.event && message.event.indexOf('onKeyboard') === 0) {
                    let _keyboardId = global.__biliCacheData.api.keyboard.current;
                    // 半屏键盘相关
                    if (_keyboardId) {
                        let _keyboardEvent = global.__biliCacheData.eventHandler.keyboard._cache[_keyboardId];
                        if (_keyboardEvent) {
                            if (typeof _keyboardEvent[message.event] === 'function') _keyboardEvent[message.event](message.data || {});
                        }
                    }
                } else {
                    const systemHandlers = eventHandlers.systemHandlers || {};
                    if(systemHandlers && systemHandlers[message.event]) {
                        systemHandlers[message.event].forEach(callback => {
                            callback(message.data);
                        });
                    }
                }
                break;
            }
        } else if (/^dom/i.test(message.type)) {
            if(Service.callbacks[message.callbackId]) {
                Service.callbacks[message.callbackId](message.result);
            }
        } else if (message.type === 'audio') {
            const audio = Audio.audios[message.id];
            if (audio) {
                if(audio.__eventListeners[message.event]) {
                    audio.__eventListeners[message.event].forEach(cb => {
                        cb.call(audio, message.data || {});
                    });
                }
            }
        } else if (message.type === 'previewImage') {
            const comp = Media.PreviewImageComponents[message.id];
            if(!comp) {
                return;
            }
            const commonFunObj = {
                updateData(options) {
                    Service.callNative('updatePreviewImageData', JSON.stringify(
                        Object.assign({ id: message.id }, options)
                    ), getUid());
                },
                closePreviewImage() {
                    Service.callNative('closePreviewImage', JSON.stringify(
                        { id: message.id }
                    ), getUid());
                }
            };

            switch (message.event) {
            case 'onBottomClick':
                if(comp.bottomFn) {
                    comp.bottomFn(Object.assign(message.data, commonFunObj));
                }
                break;
            case 'onRightDrag':
                if(comp.rightFn) {
                    comp.rightFn(commonFunObj);
                }
                break;
            case 'destroy':
                if(comp.closeFn) {
                    comp.closeFn();
                }
                delete Media.PreviewImageComponents[message.id];
                break;
            }
        } else if (message.type === 'download') {
            const callbacks = Network.downloadFileCallBacks ? (Network.downloadFileCallBacks[message.id] || {}) : {};
            if (message.event === 'success') {
                if(callbacks[message.event]) {
                    callbacks[message.event](Object.assign({
                        errMsg: 'downloadFile:ok'
                    }, message.data));
                    delete Network.downloadFileCallBacks[message.id];
                }
            } else if (message.event === 'fail') {
                if(callbacks[message.event]) {
                    callbacks[message.event]({
                        errMsg: message.data.msg
                    });
                    delete Network.downloadFileCallBacks[message.id];
                }
            } else if (message.event === 'onProgressUpdate') {
                if(callbacks.onProgressUpdateCallbacks) {
                    callbacks.onProgressUpdateCallbacks.forEach(callback => {
                        callback(message.data);
                    });
                }
            }
        } else if (message.type === 'navigation') {
            if (message.event === 'onLeftButtonClick') {
                Interface.leftButtonOnClick[pageId]();
            } else if (message.event === 'onRightButtonClick') {
                Private.navigationRightButtonOnClick[pageId](message.data.index);
            } else if (message.event === 'onConfigRightButtonClick') {
                Private.configRightButtonClick[pageId](message.data.index);
            }

        } else if (message.type === 'upload') {
            const handlers = eventHandlers.uploadFileHandlers ? (eventHandlers.uploadFileHandlers[message.id] || {}) : {};

            if (message.event === 'success') {
                if(handlers[message.event]) {
                    handlers[message.event](Object.assign({
                        errMsg: 'uploadFile:ok'
                    }, message.data));
                }
                if(handlers.complete) {
                    handlers.complete(Object.assign({
                        errMsg: 'uploadFile:ok'
                    }, message.data));
                }
                if (eventHandlers.uploadFileHandlers && eventHandlers.uploadFileHandlers[message.id]) {
                    delete eventHandlers.uploadFileHandlers[message.id];
                }
            } else if (message.event === 'fail') {
                if(handlers[message.event]) {
                    handlers[message.event]({
                        errMsg: 'uploadFile:fail ' + message.data.msg
                    });
                }
                if(handlers.complete) {
                    handlers.complete({
                        errMsg: 'uploadFile:fail ' + message.data.msg
                    });
                }
                if (eventHandlers.uploadFileHandlers && eventHandlers.uploadFileHandlers[message.id]) {
                    delete eventHandlers.uploadFileHandlers[message.id];
                }
            } else {
                if(handlers[message.event + 'Callbacks']) {
                    handlers[message.event + 'Callbacks'].forEach(callback => {
                        callback(message.data);
                    });
                }
            }
        } else if (message.type === 'updater') {
            const updateManagerHandlers = eventHandlers.updateManagerHandlers || {};
            if(updateManagerHandlers) {
                updateManagerHandlers[message.event + 'Param'] = message.data;
                if(updateManagerHandlers[message.event + 'Callbacks']) {
                    updateManagerHandlers[message.event + 'Callbacks'].forEach(callback => {
                        callback(message.data);
                    });
                }
            }
        } else if (message.type === 'canvas') {
            // 该消息通道应在6.12.0以上版本才启用，所以向下兼容判断版本
            if (compareVersion(systemInfo.SDKVersion, '3.18.0') >= 0 ) {
                let _noCanvas = global.canvas.callback;
                // console.log('-- canvas message:：Render -> Service通道 ok', message, pageId);
                if (message.behavior) {
                    // console.log('-- canvas message:：收到安卓降级行为方案消息', message);
                    if (message.behavior === 'callback') {
                        if (_noCanvas && _noCanvas[message.funId]) {
                            if (message.name === 'toTempFilePath') {
                                // 需要将h5降级的base64转成bl file类型，保持非降级一致
                                let temp = message.data.tempFilePath.replace(/^.+,/, '');
                                let tempFile = Service.callNative('_inner.toTempFilePathSync', JSON.stringify({
                                    data: temp
                                }));
                                message.data.tempFilePath = JSON.parse(tempFile).data.path;
                                _noCanvas[message.funId](Object.assign({},message.data, {
                                    errMsg: message.callType === 'fail'? `${message.name}:fail`: `${message.name}:ok`
                                }));
                            } else {
                                _noCanvas[message.funId](Object.assign({},message.data, {
                                    errMsg: message.callType === 'fail'? `${message.name}:fail`: `${message.name}:ok`
                                }));
                            }
                        }
                    } else if (message.behavior === 'setData') {
                        if (global.canvas && global.canvas[pageId] && global.canvas[pageId][message.canvasId]) {
                            global.canvas[pageId][message.canvasId].isNewCreate = false;
                        }
                    }
                } else {
                    // canvas 方案设计（初始化，创建，消费，隔离）   2020-09-14
                    if (!(global.canvas[pageId] && global.canvas[pageId][message.canvasId])) {
                        // console.log('-- canvas message:：Service存在初始化的变量 ok', message.canvasId);
                        // 防止render层，相同canvas id重复操作一个service层canvas。
                        if (global.canvas[message.canvasId].componentId) {
                            // console.log('-- canvas message:：Service层canvas绑定Render层组件已存在（防止render层多相同id组件影响） ok', message.componentId);
                            if (global.canvas[message.canvasId].componentId !== message.componentId) return;
                        } else {
                            // console.log('-- canvas message:：bind Render组件uid ok', message.componentId);
                            global.canvas[message.canvasId].componentId = message.componentId;
                        }
                        // 无降级，和NA协作
                        if (!message.isDowngrade) {
                            // console.log('-- canvas message:：Service非降级 ok');
                            // 初始化在屏canvas
                            if (message.messageType === 'create') {
                                // 缓存样式
                                global.canvas[message.canvasId].style = message.style;
                                // console.log('-- canvas message:：Render ready，开始初始化在屏canvas ok');
                                // 创建的时候缓存pageId
                                global.canvas[message.canvasId].pageId = pageId;
                                let _temp = (message.canvasSyncType === '2d' || message.canvasSyncType === 'webgl');
                                // 调用NA注入的canvas对象进行初始化，需要低版本兼容不报错
                                let _canvasForNA = global.__biliComponentData.canvas.canvasForNA();
                                if (_canvasForNA) {
                                    let canvas = _canvasForNA.createCanvas(false, pageId, _temp? 2:1);
                                    // console.log('-- canvas message:：create 在屏canvas ok', canvas);
                                    // 缓存canvas对象
                                    global.canvas[message.canvasId].canvas = canvas;
                                    // 更新层级
                                    canvas.zIndex = message.zIndex;
                                    // 更新样式
                                    canvas.style = message.style;
                                    // console.log('-- canvas message:：更新在屏canvas样式 ok', message.style);
                                    // 注册事件
                                    for (let eventKey in global.canvas[message.canvasId].event) {
                                        canvas['on'+ eventKey] = global.canvas[message.canvasId].event[eventKey];
                                    }
                                    let ctx ;
                                    // console.log('-- canvas message:：绑定在屏canvas事件 ok', canvas);
                                    // ==> 判断2期方案，支持初始化2d和webgl方案。
                                    if (!_temp) {
                                        ctx = canvas.getContext('2d');
                                        // 缓存ctx
                                        global.canvas[message.canvasId].ctx = ctx;
                                        // console.log('-- canvas message:：初始化在屏canvas getContext ok', ctx);
                                        // console.log('-- canvas message:：执行缓冲队列到在屏canvas...', global.canvas[message.canvasId].queue.length, global.canvas[message.canvasId].queue.concat([]));
                                        // 判断队列里有没有缓存消息
                                        while (global.canvas[message.canvasId].queue.length > 0) {
                                            let tempQueue = global.canvas[message.canvasId].queue.shift();
                                            // 执行ctx下方法和属性
                                            if (tempQueue.class === 'ctx') {
                                                if (tempQueue.type === 'function') {
                                                    // 有上下文
                                                    if (tempQueue.callerId) {
                                                        // 缓存渐变对象上下文
                                                        if (!global.canvas.grd) global.canvas.grd = {};
                                                        global.canvas.grd[tempQueue.callerId] = ctx[tempQueue.name](...tempQueue.arguments);
                                                    } else if (tempQueue.callerType === 'return') {
                                                        try {
                                                            if (tempQueue.name === 'putImageData') {
                                                                let tempArg = tempQueue.arguments[0]
                                                                let tempImageData = ctx.createImageData(tempArg.width, tempArg.height, tempArg.data);
                                                                ctx[tempQueue.name](tempImageData, tempArg.x, tempArg.y);
                                                                if (tempQueue.callback.success) {
                                                                    setTimeout(()=>{
                                                                        const outputValue = {
                                                                            errMsg: tempQueue.name + ':ok'
                                                                        }
                                                                        global.canvas.callback[tempQueue.callback.success](outputValue);
                                                                    }, 0);
                                                                }
                                                            } else {
                                                                let temp = ctx[tempQueue.name](...tempQueue.arguments);
                                                                if (tempQueue.callback.success) {
                                                                    setTimeout(()=>{
                                                                        const outputValue = {
                                                                            errMsg: tempQueue.name + ':ok'
                                                                        }
                                                                        let outputTemp = null
                                                                        if (typeof temp === 'object' && !Array.isArray(temp)) {
                                                                            outputTemp = Object.assign(temp, outputValue);
                                                                        } else if (temp === undefined || temp === null) {
                                                                            outputTemp = outputValue;
                                                                        } else {
                                                                            outputTemp = temp;
                                                                        }
                                                                        global.canvas.callback[tempQueue.callback.success](outputTemp);
                                                                    }, 0);
                                                                }
                                                            }
                                                        } catch (e) {
                                                            if (tempQueue.callback.fail) {
                                                                setTimeout(()=>{
                                                                    const outputValue = {
                                                                        errMsg: tempQueue.name + ':fail ' + e.message
                                                                    }
                                                                    global.canvas.callback[tempQueue.callback.fail](outputValue);
                                                                }, 0);
                                                            }
                                                        } finally {
                                                            if (tempQueue.callback.complete) {
                                                                setTimeout(()=>{
                                                                    const outputValue = {
                                                                        errMsg: tempQueue.name + ':complete'
                                                                    }
                                                                    global.canvas.callback[tempQueue.callback.complete](outputValue);
                                                                }, 0);
                                                            }
                                                        }
                                                    } else{
                                                        if (tempQueue.name === 'clearRect') {
                                                            tempQueue.arguments[2] = message.style.width
                                                            tempQueue.arguments[3] = message.style.height
                                                        }
                                                        ctx[tempQueue.name](...tempQueue.arguments);
                                                    }
                                                } else if (tempQueue.type === 'property') {
                                                    if (is.Object(tempQueue.arguments)) {
                                                        if (tempQueue.arguments.grd) {
                                                            ctx[tempQueue.name] = global.canvas.grd[tempQueue.arguments.grd];
                                                        } else {
                                                            ctx[tempQueue.name] = tempQueue.arguments;
                                                        }
                                                    } else {
                                                        ctx[tempQueue.name] = tempQueue.arguments;
                                                    }
                                                } else if (tempQueue.type === 'callback') {
                                                    // 在queue队列中，需要处理完，立马调用回调。
                                                    global.canvas.callback[tempQueue.callerId]();
                                                }
                                            } else if (tempQueue.class === 'canvas') {   // 执行canvas下方法和属性
                                                if (tempQueue.type === 'function') {
                                                    canvas[tempQueue.name](...tempQueue.arguments);
                                                } else if (tempQueue.type === 'property') {
                                                    canvas[tempQueue.name] = tempQueue.arguments;
                                                }
                                            } else if (tempQueue.class === 'grd') {     // 执行grd下方法和属性
                                                if (global.canvas.grd && global.canvas.grd[tempQueue.caller]) {
                                                    global.canvas.grd[tempQueue.caller][tempQueue.name](...tempQueue.arguments);
                                                }
                                            }
                                        }
                                        // console.log('-- canvas message:：执行缓冲队列到在屏canvas ok');
                                        // 初始化在线canvas成功，且缓存命令执行完毕。
                                    }
                                    global.canvas[message.canvasId].status = 'ready';
                                    // console.log('-- canvas message:：更新全局缓存global状态 ok', global.canvas[message.canvasId]);
                                }

                                if (!global.canvas[pageId]) global.canvas[pageId] = {};
                                global.canvas[pageId][message.canvasId] = global.canvas[message.canvasId];
                                delete global.canvas[message.canvasId];

                                // 判断是否querySelect是否缓存，如果缓存则处理完毕后再执行callback
                                if (global.__biliComponentData.canvas.waitingCanvas.length > 0) {
                                    global.__biliComponentData.canvas.checkoutQuery();
                                }
                            }
                        } else {
                            // console.log('-- canvas message:：Service降级 ok');
                            // 1. 缓存样式
                            global.canvas[message.canvasId].style = message.style;
                            // console.log('-- canvas message:：Render ready，开始缓存数据 ok');
                            // 创建的时候缓存pageId
                            global.canvas[message.canvasId].pageId = pageId;
                            // console.log('-- canvas message:：queue队列检查 ok', global.canvas[message.canvasId].queue.length);
                            if (!bl.isCanvasSupportedSync()) {
                                if (global.canvas[message.canvasId].queue.length > 0) {
                                    Service.postMessage(JSON.stringify({
                                        type: 'canvas',
                                        canvasId: message.canvasId,
                                        data: global.canvas[message.canvasId].queue,
                                        style: global.canvas[message.canvasId].style
                                    }), pageId);
                                    global.canvas[message.canvasId].queue = []
                                    // console.log('-- canvas message:：发送指令 -> render执行 ok');
                                }
                            }

                            // 初始化在线canvas成功，且缓存命令执行完毕。
                            global.canvas[message.canvasId].status = 'ready';
                            // console.log('-- canvas message:：更新全局缓存global状态 ok', global.canvas[message.canvasId]);
                            if (!global.canvas[pageId]) global.canvas[pageId] = {};
                            global.canvas[pageId][message.canvasId] = global.canvas[message.canvasId];
                            delete global.canvas[message.canvasId];

                            // 判断是否querySelect是否缓存，如果缓存则处理完毕后再执行callback
                            if (global.__biliComponentData.canvas.waitingCanvas.length > 0) {
                                global.__biliComponentData.canvas.checkoutQuery();
                            }
                        }
                    } else {
                        if (message.messageType === 'update') {
                            if (global.canvas[pageId][message.canvasId].status === 'ready') {
                                // 更新样式
                                if (message.style.hidden) {
                                    global.canvas[pageId][message.canvasId].style.hidden = message.style.hidden;
                                    if (!message.isDowngrade) {
                                        global.canvas[pageId][message.canvasId].canvas.style = global.canvas[pageId][message.canvasId].style;
                                    }
                                } else {
                                    global.canvas[pageId][message.canvasId].style = message.style;
                                    if (!message.isDowngrade) {
                                        global.canvas[pageId][message.canvasId].canvas.style =  message.style;
                                    }
                                }
                            }
                            // console.log('-- canvas message:：update在屏canvas样式 ok', global.canvas[pageId][message.canvasId].canvas);
                        } else if (message.messageType === 'create') {
                            // 防止render层，相同canvas id重复操作一个service层canvas。
                            if (global.canvas[pageId][message.canvasId].componentId) {
                                // console.log('-- canvas message:：Service层canvas绑定Render层组件已存在（防止render层多相同id组件影响） ok', message.componentId);
                                if (global.canvas[pageId][message.canvasId].componentId !== message.componentId) return;
                            } else {
                                // console.log('-- canvas message:：bind Render组件uid ok', message.componentId);
                                global.canvas[pageId][message.canvasId].componentId = message.componentId;
                            }

                            if (!message.isDowngrade) {
                                // console.log('-- canvas message:：二次创建canvas开始 ok');
                                // 已初始化过，但是用户手动进行销毁过
                                global.canvas[pageId][message.canvasId].style = message.style;
                                let _canvasForNA = global.__biliComponentData.canvas.canvasForNA();
                                let _temp = (message.canvasSyncType === '2d' || message.canvasSyncType === 'webgl');
                                if (_canvasForNA) {
                                    let canvas = _canvasForNA.createCanvas(false, pageId, _temp? 2:1);
                                    // console.log('-- canvas message:：create 在屏canvas ok', canvas);
                                    // 缓存canvas对象
                                    global.canvas[pageId][message.canvasId].canvas = canvas;
                                    // 初始化层级
                                    canvas.zIndex = message.zIndex;
                                    // 更新样式
                                    canvas.style = message.style;
                                    // console.log('-- canvas message:：更新在屏canvas样式 ok', message.style);
                                    // 注册事件
                                    for (let eventKey in global.canvas[pageId][message.canvasId].event) {
                                        canvas['on'+ eventKey] = global.canvas[pageId][message.canvasId].event[eventKey];
                                    }
                                    // console.log('-- canvas message:：绑定在屏canvas事件 ok', canvas);

                                    let ctx;
                                    // ==> 判断2期方案，支持初始化2d和webgl方案。
                                    if (!_temp) {
                                        ctx = canvas.getContext('2d');
                                        // 缓存ctx
                                        global.canvas[pageId][message.canvasId].ctx = ctx;
                                        // console.log('-- canvas message:：初始化在屏canvas getContext ok', ctx);
                                        // console.log('-- canvas message:：执行缓冲队列到在屏canvas...', global.canvas[pageId][message.canvasId].queue.length);
                                        // 判断队列里有没有缓存消息
                                        while (global.canvas[pageId][message.canvasId].queue.length > 0) {
                                            let tempQueue = global.canvas[pageId][message.canvasId].queue.shift();
                                            // 执行ctx下方法和属性
                                            if (tempQueue.class === 'ctx') {
                                                if (tempQueue.type === 'function') {
                                                    // 有上下文
                                                    if (tempQueue.callerId) {
                                                        // 缓存渐变对象上下文
                                                        if (!global.canvas.grd) global.canvas.grd = {};
                                                        global.canvas.grd[tempQueue.callerId] = ctx[tempQueue.name](...tempQueue.arguments);
                                                    } else if (tempQueue.callerType === 'return') {
                                                        try {
                                                            if (tempQueue.name === 'putImageData') {
                                                                let tempArg = tempQueue.arguments[0]
                                                                let tempImageData = ctx.createImageData(tempArg.width, tempArg.height, tempArg.data);
                                                                ctx[tempQueue.name](tempImageData, tempArg.x, tempArg.y);
                                                                if (tempQueue.callback.success) {
                                                                    setTimeout(()=>{
                                                                        const outputValue = {
                                                                            errMsg: tempQueue.name + ':ok'
                                                                        }
                                                                        global.canvas.callback[tempQueue.callback.success](outputValue);
                                                                    });
                                                                }
                                                            } else {
                                                                let temp = ctx[tempQueue.name](...tempQueue.arguments);
                                                                if (tempQueue.callback.success) {
                                                                    setTimeout(() => {
                                                                        const outputValue = {
                                                                            errMsg: tempQueue.name + ':ok'
                                                                        }
                                                                        let outputTemp = null
                                                                        if (typeof temp === 'object' && !Array.isArray(temp)) {
                                                                            outputTemp = Object.assign(temp, outputValue);
                                                                        } else if (temp === undefined || temp === null) {
                                                                            outputTemp = outputValue;
                                                                        } else {
                                                                            outputTemp = temp;
                                                                        }
                                                                        global.canvas.callback[tempQueue.callback.success](outputTemp);
                                                                    });
                                                                }
                                                            }
                                                        } catch (e) {
                                                            if (tempQueue.callback.fail) {
                                                                setTimeout(()=>{
                                                                    const outputValue = {
                                                                        errMsg: tempQueue.name + ':fail ' + e.message
                                                                    }
                                                                    global.canvas.callback[tempQueue.callback.fail](outputValue);
                                                                });
                                                            }
                                                        } finally {
                                                            if (tempQueue.callback.complete) {
                                                                setTimeout(()=>{
                                                                    const outputValue = {
                                                                        errMsg: tempQueue.name + ':complete'
                                                                    }
                                                                    global.canvas.callback[tempQueue.callback.complete](outputValue);
                                                                });
                                                            }
                                                        }
                                                    } else{
                                                        // 未ready消费queue的时候，并不知道真实canvas 宽高，需要做处理
                                                        if (tempQueue.name === 'clearRect' && tempQueue.isInit) {
                                                            ctx[tempQueue.name](0, 0, message.style.width, message.style.height);
                                                        } else {
                                                            ctx[tempQueue.name](...tempQueue.arguments);
                                                        }
                                                    }
                                                } else if (tempQueue.type === 'property') {
                                                    if (is.Object(tempQueue.arguments)) {
                                                        if (tempQueue.arguments.grd) {
                                                            ctx[tempQueue.name] = global.canvas.grd[tempQueue.arguments.grd];
                                                        } else {
                                                            ctx[tempQueue.name] = tempQueue.arguments;
                                                        }
                                                    } else {
                                                        ctx[tempQueue.name] = tempQueue.arguments;
                                                    }
                                                } else if (tempQueue.type === 'callback') {
                                                    // 在queue队列中，需要处理完，立马调用回调。
                                                    global.canvas.callback[tempQueue.callerId]();
                                                }
                                            } else if (tempQueue.class === 'canvas') {   // 执行canvas下方法和属性
                                                if (tempQueue.type === 'function') {
                                                    canvas[tempQueue.name](...tempQueue.arguments);
                                                } else if (tempQueue.type === 'property') {
                                                    canvas[tempQueue.name] = tempQueue.arguments;
                                                }
                                            } else if (tempQueue.class === 'grd') {     // 执行grd下方法和属性
                                                if (global.canvas.grd && global.canvas.grd[tempQueue.caller]) {
                                                    global.canvas.grd[tempQueue.caller][tempQueue.name](...tempQueue.arguments);
                                                }
                                            }
                                        }
                                        // console.log('-- canvas message:：执行缓冲队列到在屏canvas ok');
                                        // 初始化在线canvas成功，且缓存命令执行完毕。
                                    }

                                    global.canvas[pageId][message.canvasId].status = 'ready';
                                    // console.log('-- canvas message:：更新全局缓存global状态 ok', global.canvas[pageId][message.canvasId]);

                                    // 判断是否querySelect是否缓存，如果缓存则处理完毕后再执行callback
                                    if (global.__biliComponentData.canvas.waitingCanvas.length > 0) {
                                        global.__biliComponentData.canvas.checkoutQuery();
                                    }
                                }
                            } else {
                                // 2次销毁重建 --> 安卓降级方案
                                // console.log('-- canvas message:：Service降级 ok');
                                // 1. 缓存样式
                                global.canvas[pageId][message.canvasId].style = message.style;
                                // console.log('-- canvas message:：Render ready，开始缓存数据 ok');
                                // 创建的时候缓存pageId
                                global.canvas[pageId][message.canvasId].pageId = pageId;
                                global.canvas[pageId][message.canvasId].isNewCreate = true;
                                // console.log('-- canvas message:：queue队列检查 ok', global.canvas[pageId][message.canvasId].queue.length);
                                if (!bl.isCanvasSupportedSync()) {
                                    if (global.canvas[pageId][message.canvasId].queue.length > 0) {
                                        Service.postMessage(JSON.stringify({
                                            type: 'canvas',
                                            canvasId: message.canvasId,
                                            isNewCreate: global.canvas[pageId][message.canvasId].isNewCreate,
                                            data: global.canvas[pageId][message.canvasId].queue,
                                            style: global.canvas[pageId][message.canvasId].style
                                        }), pageId);
                                        global.canvas[pageId][message.canvasId].queue = []
                                        // console.log('-- canvas message:：发送指令 -> render执行 ok');
                                    }
                                }

                                // 初始化在线canvas成功，且缓存命令执行完毕。
                                global.canvas[pageId][message.canvasId].status = 'ready';
                                // console.log('-- canvas message:：更新全局缓存global状态 ok', global.canvas[pageId][message.canvasId]);

                                // 判断是否querySelect是否缓存，如果缓存则处理完毕后再执行callback
                                if (global.__biliComponentData.canvas.waitingCanvas.length > 0) {
                                    global.__biliComponentData.canvas.checkoutQuery();
                                }
                            }
                        }
                    }
                }
            }
        } else if(message.type === 'account') {
            if(message.event === 'login') {
                const accountLoginCallback = getAccountLoginCallback();
                accountLoginCallback.forEach(callback => {
                    callback();
                })
            } else if(message.event === 'logoff') {
                const accoutLogoffCallback = getAccountLogoffCallback();
                accoutLogoffCallback.forEach(callback => {
                    callback();
                })
            }
        } else if(message.type === 'callback') {
            if (message.callbackId) {
                Service.callbacks[message.callbackId](message.result);
                delete Service.callbacks[message.callbackId];
            }
        } else if(message.type === 'channel') {
            // 新消息通道
            global["__bili_channel"].trigger(message.key, message.data);
        } else {
            // type = page-event
            // ios keyboard
            if (message.event === 'onKeyboardChange') {
                if(eventHandlers.keyboardHandlers) {
                    eventHandlers.keyboardHandlers.forEach(callback => {
                        callback(message.data);
                    });
                }
            } else if (message.event === 'onOrientationChange') {
                const currentPage = getPropertyBySafe(Service, 'location.currentPage');
                const id = global.__biliCacheData.getCurrentPageId();
                if (currentPage && id === pageId) {
                    if(eventHandlers.deviceHandlers && eventHandlers.deviceHandlers.orientationChange) {
                        eventHandlers.deviceHandlers.orientationChange.forEach(callback => {
                            callback(message.data);
                        });
                    }
                }
            } else if(message.event === 'onWindowResize') {
                const currentPage = getPropertyBySafe(Service, 'location.currentPage');
                const id = global.__biliCacheData.getCurrentPageId();
                if (currentPage && id === pageId) {
                    if(eventHandlers.windowResizeHandlers) {
                        eventHandlers.windowResizeHandlers.forEach(callback => {
                            callback(message.data);
                        });
                    }
                }
            } else if (message.type && pageId) {
                if (message.event === 'onResize') {
                    // 强制界面刷新，触发MutationObserver
                    Service.postMessage(JSON.stringify({
                        type: 'fe-framework',
                        event: 'triggerMutationObserver'
                    }),global.__biliCacheData.getCurrentPageId())
                }
                if(Page.pages[pageId] && Page.pages[pageId].handleMessage) {
                    Page.pages[pageId].handleMessage(message);
                }
            }
        }
    } catch (e) {
        Service.onError(e, true);
        showSystemLog && console.error(e);
        throw e;
    }
};

function tryAddQuery(obj, key) {
    try {
        const [baseUrl, query = ''] = obj[key].split('?');
        if (!/___timestamp=/.test(query)) {
            obj[key] = `${baseUrl}?___timestamp=${Date.now()}${query ? '&' + query : ''}`;
        }
    } catch (error) {
        showSystemLog && console.log(error);
    }
}

Service.handleMessageWithReturn = function(isString, message, pageId) {
    showSystemLog && console.log('Service.handleMessageWithReturn', message, pageId);
    let result;
    try {
        if (message.type === 'system') {
            switch (message.event) {
            case 'onShareAppMessage': {
                const { appId, virtualId } = bl.getLaunchOptionsSync();
                const isInternal = appId.indexOf('bilinternal') === 0;
                let returnConfig = {};
                const onShareMessage = getPropertyBySafe(Service, 'location.currentPage.$options.onShareAppMessage');
                const onInternalShareAppMessage = getPropertyBySafe(Service, 'location.currentPage.$options.onInternalShareAppMessage');

                if (isInternal) {
                    if (onInternalShareAppMessage) {
                        returnConfig = onInternalShareAppMessage.call(Service.location.currentPage) || {};

                        if (returnConfig.data) {
                            const conf = ['weixin', 'weixin_monment', 'qq', 'sina', 'q_zone', 'copy', 'default'];

                            tryAddQuery(returnConfig.data.dynamic && returnConfig.data.dynamic.sketch, 'target_url');
                            conf.forEach(item => {
                                tryAddQuery(returnConfig.data[item], 'url');
                            });
                        }
                    } else if (onShareMessage) {
                        returnConfig = onShareMessage.call(Service.location.currentPage) || {};
                    }
                } else if (onShareMessage) {
                    returnConfig = onShareMessage.call(Service.location.currentPage) || {};
                }

                const PREFIX = bl.getLaunchOptionsSync().envVersion;
                let [baseUrl, query = ''] = returnConfig.path
                    ? returnConfig.path.split('?')
                    : Service.location.currentPage.$root.$page.uri.split('?');

                query = makeShareQuery(query);

                result = {
                    title: returnConfig.title,
                    content: returnConfig.content,
                    imageUrl: returnConfig.imageUrl,
                    biliContent: returnConfig.biliContent,
                    biliMessageTitle: returnConfig.biliMessageTitle,
                    linkUrl: `https://mall.bilibili.com/miniapp/${PREFIX ? PREFIX + '-' : ''}${appId}${virtualId ? '_' + virtualId : ''}${baseUrl}${query}`,
                    hasInternalData: isInternal && onInternalShareAppMessage,
                    internalData: isInternal && onInternalShareAppMessage ? returnConfig.data : {},
                };
                break;
            }
            }
        } else if(message.type === 'adjustableWebView') {
            // console.log(message.data, 'massage.data')
            if(message.data) {
                try {
                    const parentId = JSON.parse(message.data.biliExtra).parent;
                    const component = Service.components[parentId];
                    let fn = component[JSON.parse(message.data.biliExtra).enableNavigate]
                    if(component && fn && typeof fn === 'function') {
                        // console.log(component, 'component')
                        return fn(message.data);
                    }
                } catch(e) {
                    console.log(e)
                }
            }
        }

        return isString ? JSON.stringify(result) : result;
    } catch (e) {
        showSystemLog && console.error(e);
    }
};

const empty = () => {};
const clearAccessKey = () => {
    bl && typeof bl.internal.clearAccessKey === 'function' && bl.internal.clearAccessKey();
};
const PreProcess = {
    onLaunch: (callback, isSpecial) => {
        if (isSpecial) {
            return function(options) {
                const option = options.options || options;
                option.path = option.path.split('?')[0];
                option.query = parseQuery('?' + option.query);
                option.referrerInfo = option.referrerInfo || {};

                if (!Service.__specialStorage.done) {
                    Service.__specialStorage.callbackStack.push({
                        cb: callback.bind(bl.appInstance),
                        params: [option],
                    });
                } else {
                    callback.call(bl.appInstance, option);
                }
            };
        }

        return function(options) {
            const option = options.options || options;
            option.path = option.path.split('?')[0];
            option.query = parseQuery('?' + option.query);
            option.referrerInfo = option.referrerInfo || {};

            if (!global.__smallApp) global.__smallApp = {};
            global.__smallApp.enterOptions = option;
            callback.call(bl.appInstance, option);
        };
    },
    onShow: (callback, isSpecial) => {
        if (isSpecial) {
            return function(options) {
                !Service.__specialStorage.done &&
                bl.getStorage({
                    key: '__bugfix',
                    complete() {
                        Service.__specialStorage.done = true;
                        while (Service.__specialStorage.callbackStack.length) {
                            const cbObj = Service.__specialStorage.callbackStack.shift();
                            cbObj.cb(...cbObj.params);
                        }
                    },
                });

                const option = options.options || options;
                option.path = option.path.split('?')[0];
                option.query = parseQuery('?' + option.query);
                option.referrerInfo = option.referrerInfo || {};

                if (!Service.__specialStorage.done) {
                    Service.__specialStorage.callbackStack.push({
                        cb: callback.bind(bl.appInstance),
                        params: [option],
                    });
                } else {
                    callback.call(bl.appInstance, option);
                }
            };
        }

        return function(options) {
            const option = options.options || options;
            option.path = option.path.split('?')[0];
            option.query = parseQuery('?' + option.query);
            option.referrerInfo = option.referrerInfo || {};

            global.__smallApp.enterOptions = Object.assign(global.__smallApp.enterOptions, options);
            callback.call(bl.appInstance, option);
        };
    },
    onHide: callback => {
        return function() {
            // 清除 access key
            clearAccessKey();

            Service.__specialStorage.done = false;
            callback.call(bl.appInstance);
        };
    },
};

// 小程序初始化完成时（全局只触发一次）也就是jscore加载完service.js
Service.onLaunch = PreProcess.onLaunch(empty, Service.__specialStorage.isSpecial);

// 小程序启动，或从后台进入前台显示时
Service.onShow = PreProcess.onShow(empty, Service.__specialStorage.isSpecial);

// 小程序从前台进入后台时
Service.onHide = Service.__specialStorage.isSpecial ? PreProcess.onHide(empty) : clearAccessKey;

// 小程序发生脚本错误，或者 api 调用失败时触发，会带上错误信息
// 安卓上 msg 和 isFromJs 会反过来，很奇怪
Service.onError = (msg, isFromJs = false) => {
    if (isiOS) {
        showSystemLog && console.info(`onError from ${isFromJs ? 'js' : 'NA'}`);
    } else {
        showSystemLog && console.info(`onError from ${msg ? 'js' : 'NA'}`);
    }

    eventHandlers.onErrorHandlers.forEach(handler => {
        handler(!isiOS && !isFromJs ? isFromJs : msg);
    });
};

// 小程序要打开的页面不存在时触发，会带上页面信息回调该函数
Service.onPageNotFound = empty;

showSystemLog && console.log(Service);

App = hooks => {
    if (hooks && typeof hooks === 'object') {
        bl.appInstance = hooks;
        ['onLaunch', 'onShow', 'onHide', 'onError', 'onPageNotFound', 'onThemeChange'].forEach(hook => {
            if (typeof hooks[hook] === 'function') {
                // 5.38iOS的异常处理
                if (Service.__specialStorage.isSpecial && ['onLaunch', 'onShow', 'onHide'].indexOf(hook) >= 0) {
                    Service[hook] = PreProcess[hook](hooks[hook], true);
                } else if (['onLaunch', 'onShow'].indexOf(hook) >= 0) {
                    Service[hook] = PreProcess[hook](hooks[hook], false);
                } else if (['onError'].indexOf(hook) >= 0) {
                    eventHandlers.onErrorHandlers.push(hooks[hook].bind(bl.appInstance));
                } else if(['onThemeChange'].indexOf(hook) >= 0) {
                    eventHandlers.systemHandlers.onThemeChange.push(hooks[hook].bind(bl.appInstance));
                } else {
                    Service[hook] = hooks[hook].bind(bl.appInstance);
                }
            }
        });
    }
};
// service-loader
// import 'bili-small-app/runtime/header';
// import '@/src/app.js';
