import { invokeArrayFns, isUniLifecycleHook, ON_LOAD, ON_SHOW, LINEFEED, RENDERJS_MODULES, formatLog, WXS_PROTOCOL, WXS_MODULES, UniLifecycleHooks, ON_ERROR, invokeCreateErrorHandler, invokeCreateVueAppHook } from '@dcloudio/uni-shared';
import { isString, isArray, isFunction } from '@vue/shared';
import { injectHook } from 'vue';

function getCurrentPage() {
    const pages = getCurrentPages();
    const len = pages.length;
    if (len) {
        return pages[len - 1];
    }
}
function getCurrentPageVm() {
    const page = getCurrentPage();
    if (page) {
        return page.$vm;
    }
}

function invokeHook(vm, name, args) {
    if (isString(vm)) {
        args = name;
        name = vm;
        vm = getCurrentPageVm();
    }
    else if (typeof vm === 'number') {
        const page = getCurrentPages().find((page) => page.$page.id === vm);
        if (page) {
            vm = page.$vm;
        }
        else {
            vm = getCurrentPageVm();
        }
    }
    if (!vm) {
        return;
    }
    // 兼容 nvue
    {
        if (vm.__call_hook) {
            return vm.__call_hook(name, args);
        }
    }
    const hooks = vm.$[name];
    return hooks && invokeArrayFns(hooks, args);
}

function injectLifecycleHook(name, hook, publicThis, instance) {
    if (isFunction(hook)) {
        injectHook(name, hook.bind(publicThis), instance);
    }
}
function initHooks(options, instance, publicThis) {
    var _a;
    const mpType = options.mpType || publicThis.$mpType;
    if (!mpType || mpType === 'component') {
        // 仅 App,Page 类型支持在 options 中配置 on 生命周期，组件可以使用组合式 API 定义页面生命周期
        return;
    }
    Object.keys(options).forEach((name) => {
        if (isUniLifecycleHook(name, options[name], false)) {
            const hooks = options[name];
            if (isArray(hooks)) {
                hooks.forEach((hook) => injectLifecycleHook(name, hook, publicThis, instance));
            }
            else {
                injectLifecycleHook(name, hooks, publicThis, instance);
            }
        }
    });
    if (mpType === 'page') {
        instance.__isVisible = true;
        // 直接触发页面 onLoad、onShow 组件内的 onLoad 和 onShow 在注册时，直接触发一次
        try {
            invokeHook(publicThis, ON_LOAD, instance.attrs.__pageQuery);
            delete instance.attrs.__pageQuery;
            if (((_a = publicThis.$page) === null || _a === void 0 ? void 0 : _a.openType) !== 'preloadPage') {
                invokeHook(publicThis, ON_SHOW);
            }
        }
        catch (e) {
            console.error(e.message + LINEFEED + e.stack);
        }
    }
}

function initRenderjs(options, instance) {
    initModules(instance, options.$renderjs, options['$' + RENDERJS_MODULES]);
}
function initModules(instance, modules, moduleIds = {}) {
    if (!isArray(modules)) {
        return;
    }
    const ownerId = instance.uid;
    // 在vue的定制内核中，通过$wxsModules来判断事件函数源码中是否包含该模块调用
    // !$wxsModules.find(module => invokerSourceCode.indexOf('.' + module + '.') > -1)
    const $wxsModules = (instance.$wxsModules ||
        (instance.$wxsModules = []));
    const ctx = instance.ctx;
    modules.forEach((module) => {
        if (moduleIds[module]) {
            ctx[module] = proxyModule(ownerId, moduleIds[module], module);
            $wxsModules.push(module);
        }
        else {
            if ((process.env.NODE_ENV !== 'production')) {
                console.error(formatLog('initModules', modules, moduleIds));
            }
        }
    });
}
function proxyModule(ownerId, moduleId, module) {
    const target = {};
    return new Proxy(target, {
        get(_, p) {
            return (target[p] ||
                (target[p] = createModuleFunction(ownerId, moduleId, module, p)));
        },
    });
}
function createModuleFunction(ownerId, moduleId, module, name) {
    const target = () => { };
    const toJSON = () => WXS_PROTOCOL + JSON.stringify([ownerId, moduleId, module + '.' + name]);
    return new Proxy(target, {
        get(_, p) {
            if (p === 'toJSON') {
                return toJSON;
            }
            return (target[p] ||
                (target[p] = createModuleFunction(ownerId, moduleId, module + '.' + name, p)));
        },
        apply(_target, _thisArg, args) {
            return (WXS_PROTOCOL +
                JSON.stringify([ownerId, moduleId, module + '.' + name, [...args]]));
        },
    });
}

function initWxs(options, instance) {
    initModules(instance, options.$wxs, options['$' + WXS_MODULES]);
}

function applyOptions(options, instance, publicThis) {
    {
        initWxs(options, instance);
        initRenderjs(options, instance);
    }
    initHooks(options, instance, publicThis);
}

function set(target, key, val) {
    return (target[key] = val);
}

function createErrorHandler(app) {
    return function errorHandler(err, instance, _info) {
        if (!instance) {
            throw err;
        }
        const appInstance = app._instance;
        if (!appInstance || !appInstance.proxy) {
            throw err;
        }
        {
            invokeHook(appInstance.proxy, ON_ERROR, err);
        }
    };
}
function mergeAsArray(to, from) {
    return to ? [...new Set([].concat(to, from))] : from;
}
function initOptionMergeStrategies(optionMergeStrategies) {
    UniLifecycleHooks.forEach((name) => {
        optionMergeStrategies[name] = mergeAsArray;
    });
}

let realAtob;
const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const b64re = /^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/;
if (typeof atob !== 'function') {
    realAtob = function (str) {
        str = String(str).replace(/[\t\n\f\r ]+/g, '');
        if (!b64re.test(str)) {
            throw new Error("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
        }
        // Adding the padding if missing, for semplicity
        str += '=='.slice(2 - (str.length & 3));
        var bitmap;
        var result = '';
        var r1;
        var r2;
        var i = 0;
        for (; i < str.length;) {
            bitmap =
                (b64.indexOf(str.charAt(i++)) << 18) |
                    (b64.indexOf(str.charAt(i++)) << 12) |
                    ((r1 = b64.indexOf(str.charAt(i++))) << 6) |
                    (r2 = b64.indexOf(str.charAt(i++)));
            result +=
                r1 === 64
                    ? String.fromCharCode((bitmap >> 16) & 255)
                    : r2 === 64
                        ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255)
                        : String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255, bitmap & 255);
        }
        return result;
    };
}
else {
    // 注意atob只能在全局对象上调用，例如：`const Base64 = {atob};Base64.atob('xxxx')`是错误的用法
    realAtob = atob;
}
function b64DecodeUnicode(str) {
    return decodeURIComponent(realAtob(str)
        .split('')
        .map(function (c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    })
        .join(''));
}
function getCurrentUserInfo() {
    const token = uni.getStorageSync('uni_id_token') || '';
    const tokenArr = token.split('.');
    if (!token || tokenArr.length !== 3) {
        return {
            uid: null,
            role: [],
            permission: [],
            tokenExpired: 0,
        };
    }
    let userInfo;
    try {
        userInfo = JSON.parse(b64DecodeUnicode(tokenArr[1]));
    }
    catch (error) {
        throw new Error('获取当前用户信息出错，详细错误信息为：' + error.message);
    }
    userInfo.tokenExpired = userInfo.exp * 1000;
    delete userInfo.exp;
    delete userInfo.iat;
    return userInfo;
}
function uniIdMixin(globalProperties) {
    globalProperties.uniIDHasRole = function (roleId) {
        const { role } = getCurrentUserInfo();
        return role.indexOf(roleId) > -1;
    };
    globalProperties.uniIDHasPermission = function (permissionId) {
        const { permission } = getCurrentUserInfo();
        return this.uniIDHasRole('admin') || permission.indexOf(permissionId) > -1;
    };
    globalProperties.uniIDTokenValid = function () {
        const { tokenExpired } = getCurrentUserInfo();
        return tokenExpired > Date.now();
    };
}

function initApp(app) {
    const appConfig = app._context.config;
    appConfig.errorHandler = invokeCreateErrorHandler(app, createErrorHandler);
    initOptionMergeStrategies(appConfig.optionMergeStrategies);
    const globalProperties = appConfig.globalProperties;
    {
        uniIdMixin(globalProperties);
    }
    {
        globalProperties.$set = set;
        globalProperties.$applyOptions = applyOptions;
    }
    {
        invokeCreateVueAppHook(app);
    }
}

export { initApp };
