import * as path from 'path';
import * as fs from 'fs-extra';
import * as babel from '@babel/core';
import * as vfs from '../fs';
import * as vms from '../ms';
import * as compiler from 'vue-template-compiler';
import * as utils from '../utils';

export * from './nuims';

export async function addLayout(fullPath: string, nodePath: string, type: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();

    vueFile.parseTemplate();

    let tplPath = path.resolve(__dirname, `../../snippets/${type}.vue`);
    let tpl = await fs.readFile(tplPath, 'utf8');
    tpl = tpl.replace(/^<template>\s+/, '').replace(/\s+<\/template>$/, '') + '\n';
    const rootEl = vueFile.templateHandler.ast;
    const selectedEl = vueFile.templateHandler.findByNodePath(nodePath, rootEl) as compiler.ASTElement;
    selectedEl.children.push(compiler.compile(tpl).ast);

    await vueFile.save();
}

/**
 * 添加页面时初始化布局
 * @param fullPath Vue 文件路径
 * @param type 布局类型
 */
export async function initLayout(fullPath: string, type: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();

    let tplPath = path.resolve(__dirname, `../../snippets/${type}.vue`);
    let tpl = await fs.readFile(tplPath, 'utf8');
    tpl = tpl.replace(/^<template>\s*/, '').replace(/\s*<\/template>\s*$/, '') + '\n';

    if (type.startsWith('grid-'))
        tpl = `<u-grid-layout>${tpl}</u-grid-layout>\n`;
    vueFile.template = tpl;

    await vueFile.save();
}

/**
 * 添加页面时初始化布局
 * @param fullPath Vue 文件路径
 * @param type 布局类型
 */
async function initViewLayout(fullPath: string, type: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();

    vueFile.parseTemplate();

    let tplPath = path.resolve(__dirname, `../../snippets/${type}.vue`);
    let tpl = await fs.readFile(tplPath, 'utf8');
    tpl = tpl.replace(/^<template>\s+/, '').replace(/\s+<\/template>$/, '') + '\n';

    const rootEl = vueFile.templateHandler.ast;
    rootEl.children.unshift(compiler.compile(tpl).ast);

    await vueFile.save();
}


export async function addCode(fullPath: string, nodePath: string, tpl: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();

    vueFile.parseTemplate();

    tpl = tpl.replace(/^<template>\s+/, '').replace(/\s+<\/template>$/, '');
    const rootEl = vueFile.templateHandler.ast;
    const selectedEl = vueFile.templateHandler.findByNodePath(nodePath, rootEl) as compiler.ASTElement;
    selectedEl.children.push(compiler.compile(tpl).ast);

    await vueFile.save();
}

export async function openFile(fullPath: string) {
    return fs.readFile(fullPath, 'utf8');
}

export async function saveFile(fullPath: string, content: string) {
    return fs.writeFile(fullPath, content);
}

export async function ensureHotReload(fullPath: string) {
    if (!fs.existsSync(fullPath))
        return;
    return fs.writeFile(fullPath, await fs.readFile(fullPath, 'utf8'))
}

// export async function mergeBlock(fullPath: string, type: string) {
//     const vueFile = new vfs.VueFile(fullPath);
//     await vueFile.open();

//     vueFile.parseTemplate();

//     let tplPath = path.resolve(__dirname, `../../snippets/${type}.vue`);
//     let tpl = await fs.readFile(tplPath, 'utf8');
//     tpl = tpl.replace(/^<template>\s+/, '').replace(/\s+<\/template>$/, '');
//     const rootEl = vueFile.templateHandler.ast;
//     rootEl.children.push(compiler.compile(tpl).ast);

//     await vueFile.save();
// }

export interface ViewInfo {
    fullPath: string,
    viewType: vfs.ViewType,
    // viewsPath?: string,
    routePath?: string,
    // vueFilePath?: string,
}

export interface AddParams {
    name: string,
    title: string,
    ext?: string,
    layout?: string,
    crumb?: string,
    first?: boolean,
}
function hasNewParams(params: AddParams) {
    return params.hasOwnProperty('title') || params.hasOwnProperty('crumb') || params.hasOwnProperty('first');
}

async function initView(viewInfo: ViewInfo) {
    const isDirectory = !(viewInfo.viewType === vfs.ViewType.vue || viewInfo.viewType === vfs.ViewType.md);
    return new vfs.View(viewInfo.fullPath, viewInfo.viewType, isDirectory, viewInfo.routePath);
}


interface MetaData {
    getMetaData(viewInfo: vfs.View, baseViewInfo?: ViewInfo | vfs.View): void,
    saveMetaData(viewInfo: vfs.View, params: AddParams, baseViewInfo?: ViewInfo | vfs.View): void,
}
class EntryMetaData implements MetaData {
    async getMetaData(viewInfo: vfs.View) {
        const fullPath = viewInfo.fullPath;
        // @TODO?
        const index = fullPath.indexOf('src');
        const pagesJSONPath = path.join(fullPath.slice(0, index), 'pages.json');
        
        const data = {
            title: '',
        }
        if (!fs.existsSync(pagesJSONPath))
            throw new Error('Cannot find pagesJSONPath');

        const pagesJSON = JSON.parse(await fs.readFile(pagesJSONPath, 'utf8'));
        data.title = pagesJSON[viewInfo.baseName] && pagesJSON[viewInfo.baseName].title;

        return data;
    }
    async saveMetaData(viewInfo: vfs.View, params: AddParams) {
        const fullPath = viewInfo.fullPath;

        const index = fullPath.indexOf('src');
        const pagesJSONPath = path.join(fullPath.slice(0, index), 'pages.json');

        if (!fs.existsSync(pagesJSONPath))
            throw new Error('Cannot find pagesJSONPath');

        const pagesJSON = JSON.parse(await fs.readFile(pagesJSONPath, 'utf8'));
        if (pagesJSON[viewInfo.baseName])
            Object.assign(pagesJSON[viewInfo.baseName], params);
            
        return fs.writeFile(pagesJSONPath, JSON.stringify(pagesJSON, null, 4));
    }
}

class PageMetaData implements MetaData {
    async getMetaData(viewInfo: vfs.View, baseViewInfo: ViewInfo | vfs.View) {
        if (!baseViewInfo)
            return {};
        const baseViewPath = baseViewInfo.fullPath;
        const routePath = path.join(baseViewPath, 'routes.map.js');
        const data = {
            title: '',
            first: false,
            meta: {},
        }
        if (fs.existsSync(routePath)) {
            let routeJSON = utils.JS.parse(await fs.readFile(routePath, 'utf8'));
            let currentPath = viewInfo.routePath.replace(baseViewInfo.routePath, '').replace(/\/$/, '');
            if (routeJSON[currentPath]) {
                data.meta = routeJSON[currentPath].meta;
                data.title = data.meta && routeJSON[currentPath].meta.title;
            }
        }
        return data;
    }
    async saveMetaData(viewInfo: vfs.View, params: AddParams, baseViewInfo?: vfs.View) {
        if (!baseViewInfo)
            return {};
        const baseViewPath = baseViewInfo.fullPath;
        const routePath = path.join(baseViewPath, 'routes.map.js');

        let routeJSON: { [name: string]: { meta?: { title: string, crumb?: string }, first?: boolean } } = {};
        if (fs.existsSync(routePath))
            routeJSON = utils.JS.parse(await fs.readFile(routePath, 'utf8'));

        let currentPath = viewInfo.routePath.replace(baseViewInfo.routePath, '').replace(/\/$/, '');
        if (!routeJSON[currentPath])
            routeJSON[currentPath] = {};
        routeJSON[currentPath].meta = Object.assign(routeJSON[currentPath].meta || {});
        if (params.hasOwnProperty('title'))
            routeJSON[currentPath].meta.title = params.title;
        if (params.hasOwnProperty('crumb'))
            routeJSON[currentPath].meta.crumb = params.crumb;
        if (params.hasOwnProperty('first'))
            routeJSON[currentPath].first = params.first;

        return fs.writeFile(routePath, 'export default ' + utils.JS.stringify(routeJSON, null, 4));
    }
}

async function getMetaData(viewInfo: vfs.View, baseViewInfo?: ViewInfo | vfs.View) {
    let instance;
    let meta = {};
    if (viewInfo.viewType === 'entry') {
        instance = new EntryMetaData();
        meta = await instance.getMetaData(viewInfo);
    } else if(viewInfo.viewType === 'branch' || viewInfo.viewType === 'vue') {
        instance = new PageMetaData();
        meta = await instance.getMetaData(viewInfo, baseViewInfo);
    }
    Object.assign(viewInfo, meta);
    return viewInfo;
}

export async function saveMetaData(viewInfo: ViewInfo | vfs.View, params: AddParams, baseViewInfo?: vfs.View){
    const view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
    let instance;
    if (view.viewType === 'entry') {
        instance = new EntryMetaData();
    } else if(view.viewType === 'branch' || view.viewType === 'vue') {
        instance = new PageMetaData();
    }
    return instance.saveMetaData(view, params, baseViewInfo);
}

/**
 * 获取页面列表
 * @param viewInfo 父页面的信息
 */
export async function loadViews(viewInfo: ViewInfo | vfs.View, baseViewInfo?: ViewInfo | vfs.View) {
    const view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
    await view.open();
    await Promise.all(view.children.map(async (child) => {
        await child.preOpen();
        return await getMetaData(child, baseViewInfo);
    }));
    return view.children;
}

export async function loadAllViews(viewInfo: ViewInfo | vfs.View) {
    const view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
    await view.open();

    if (view.children) {
        await Promise.all(view.children.map(async (child) => {
            await child.open();
            await getMetaData(child);
            await loadAllViews(child);
        }));
    }

    return view;
}

/**
 * 获取页面内容
 * @param viewInfo 父页面的信息
 */
export async function getViewContent(viewInfo: ViewInfo | vfs.View) {
    const view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
    await view.preOpen();
    const vueFile = new vfs.VueFile(view.vueFilePath);
    await vueFile.open();
    return vueFile;
}

/**
 * 保存页面内容
 * @param viewInfo 父页面的信息
 * @param content 页面代码内容
 */
export async function saveViewContent(viewInfo: ViewInfo | vfs.View, content: string) {
    const view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
    await view.preOpen();
    return fs.writeFile(view.vueFilePath, content);
}

/**
 * 保存 Vue 局部代码
 * @param fullPath Vue 文件全路径
 * @param type 内容类型
 * @param content 代码内容
 */
export async function saveCode(fullPath: string, type: 'template' | 'script' | 'style', content: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();

    if (type === 'template')
        vueFile.template = content;
    else if (type === 'script')
        vueFile.script = content;
    else if (type === 'style')
        vueFile.style = content;

    await vueFile.save();
}

export async function mergeCode(fullPath: string, content: string | vfs.VueFile, nodePath?: string) {
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();
    vueFile.parseAll();

    const blockVue = typeof content === 'string' ? vfs.VueFile.from(content) : content;
    blockVue.parseAll();
    vueFile.merge(blockVue, nodePath);
    await vueFile.save();
}

export function findRouteObjectAndParentArray(objectExpression: babel.types.ObjectExpression, relativePath: string | Array<string>, createChildrenArrayIfNeeded: boolean = false, pos: number = 0): {
    routeObject: babel.types.ObjectExpression,
    parentArray: babel.types.ArrayExpression,
} {
    const arr  = Array.isArray(relativePath) ? relativePath : relativePath.split('/');
    if (arr[pos] === 'views')
        pos++;

    if (pos === arr.length)
        throw new Error('Route path error. Cannot find route: ' + arr.join('/'));

    const ext = path.extname(arr[arr.length - 1]);
    const nextName =  arr[pos].replace(/\.[^.]*?$/, '');
    let childrenProperty = objectExpression.properties.find((property) => property.type === 'ObjectProperty'
        && property.key.type === 'Identifier' && property.key.name === 'children') as babel.types.ObjectProperty;
    if (!childrenProperty) {
        if (createChildrenArrayIfNeeded) {
            childrenProperty = babel.types.objectProperty(babel.types.identifier('children'), babel.types.arrayExpression([]));
            objectExpression.properties.push(childrenProperty);
        } else
            return { routeObject: undefined, parentArray: undefined };
    }

    const arrayExpression = childrenProperty.value as babel.types.ArrayExpression;
    const routeObject = arrayExpression.elements.find((element) => {
        return ((element.type === 'ObjectExpression' && element.properties.some((property) => property.type === 'ObjectProperty'
             && property.key.type === 'Identifier' && property.key.name === 'path'
             && property.value.type === 'StringLiteral' && property.value.value === nextName))
            || (element.type === 'ObjectExpression' && element.properties.some((property) => property.type === 'ObjectProperty'
                && property.key.type === 'Identifier' && property.key.name === 'component'
                && property.value.type === 'ArrowFunctionExpression'
                && ((property.value.body as babel.types.CallExpression).arguments[0] as babel.types.StringLiteral).value === './' + arr.slice(0, pos + 1).join('/') + (arr[pos].endsWith(ext) ? '' : '/index' + ext)))
        );
    }) as babel.types.ObjectExpression;

    if (pos === arr.length - 1) {
        return { routeObject, parentArray: arrayExpression };
    } else {
        if (!routeObject)
            return { routeObject: undefined, parentArray: undefined };
        else
            return findRouteObjectAndParentArray(routeObject, arr, createChildrenArrayIfNeeded, pos + 1);
    }
}

export async function addLeafViewRoute(parent: vfs.View, baseView: vfs.View, params: AddParams) {
    const routesPath = path.join(baseView.fullPath, 'routes.js');
    const routesMapPath = path.join(baseView.fullPath, 'routes.map.js');
    if (!fs.existsSync(routesPath) || fs.existsSync(routesMapPath))
        return;

    const jsFile = new vfs.JSFile(routesPath);
    await jsFile.open();
    const $js = jsFile.parse();

    const relativePath = path.relative(baseView.fullPath, path.join(parent.fullPath, parent.viewsPath, params.name + params.ext)).replace(/\\/g, '/');
    let changed = false;
    const exportDefault = $js.export().default();
    if (exportDefault.is('object')) {
        const { routeObject, parentArray } = findRouteObjectAndParentArray(exportDefault.node as babel.types.ObjectExpression, relativePath, true);

        if (parentArray && !routeObject) {
            const tpl = babel.parse(`[{
                path: '${params.name}',
                component: () => import(/* webpackChunkName: '${baseView.baseName}' */ './${relativePath}'),
                ${params.title ? "meta: { title: '" + params.title + "' }," : ''}
            }]`, {
                filename: 'file.js',
                plugins: [require('@babel/plugin-syntax-dynamic-import')]
            }) as babel.types.File;

            const element = ((tpl.program.body[0] as babel.types.ExpressionStatement).expression as babel.types.ArrayExpression).elements[0] as babel.types.ObjectExpression;
            parentArray.elements.push(element);
            changed = true;
        }
    }
    if (changed)
        await jsFile.save();

    return;
}

export async function addLeafView(parent: vfs.View, params: AddParams): Promise<string>;
export async function addLeafView(parent: vfs.View, baseView: vfs.View, params: AddParams): Promise<string>;
export async function addLeafView(parentInfo: ViewInfo, baseViewInfo: ViewInfo, params: AddParams): Promise<string>;
export async function addLeafView(parentInfo: ViewInfo | vfs.View, baseViewInfo: ViewInfo | vfs.View | AddParams, params?: AddParams) {
    let parent: vfs.View;
    let baseView: vfs.View;
    if (!params) {
        parent = parentInfo as vfs.View;
        params = baseViewInfo as AddParams;
        baseView = parent;
        while (baseView && baseView.viewType !== vfs.ViewType.entry)
            baseView = baseView.parent;
        if (!baseView)
            return;
    } else {
        parent = parentInfo instanceof vfs.View ? parentInfo : await initView(parentInfo);
        baseView = baseViewInfo instanceof vfs.View ? baseViewInfo : await initView(baseViewInfo as ViewInfo);
        await parent.preOpen();
        await baseView.preOpen();
    }
    params.ext = params.ext || '.vue';

    // parent view 必然是个目录
    const dest = path.join(parent.fullPath, parent.viewsPath, params.name + params.ext);

    let tplPath;
    if (params.ext === '.vue')
        tplPath = path.resolve(__dirname, '../../templates/leaf-view.vue');
    else if (params.ext === '.md')
        tplPath = path.resolve(__dirname, '../../templates/leaf-view.md');
    await fs.copy(tplPath, dest);

    if (params.layout)
        await initViewLayout(dest, params.layout);

    if (baseView) {
        await addLeafViewRoute(parent, baseView, params);

        if (hasNewParams(params)) {
            await saveMetaData({
                fullPath: dest,
                viewType: params.ext === '.vue' ? vfs.ViewType.vue : vfs.ViewType.md,
                routePath: parent.routePath + params.name,
            }, params, baseView);
        } else {
            await ensureHotReload(path.join(baseView.fullPath, 'routes.map.js'));
        }
    }

    return dest;
}

export async function addBranchViewRoute(parent: vfs.View, baseView: vfs.View, params: AddParams) {
    const routesPath = path.join(baseView.fullPath, 'routes.js');
    const routesMapPath = path.join(baseView.fullPath, 'routes.map.js');
    if (!fs.existsSync(routesPath) || fs.existsSync(routesMapPath))
        return;

    const jsFile = new vfs.JSFile(routesPath);
    await jsFile.open();
    const $js = jsFile.parse();

    // 纯目录，不带 /index.vue 的
    const relativePath = path.relative(baseView.fullPath, path.join(parent.fullPath, parent.viewsPath, params.name)).replace(/\\/g, '/');
    let changed = false;
    const exportDefault = $js.export().default();
    if (exportDefault.is('object')) {
        const { routeObject, parentArray } = findRouteObjectAndParentArray(exportDefault.node as babel.types.ObjectExpression, relativePath, true);

        if (parentArray && !routeObject) {
            const tpl = babel.parse(`[{
                path: '${params.name}',
                component: () => import(/* webpackChunkName: '${baseView.baseName}' */ './${relativePath + '/index' + params.ext}'),
                ${params.title ? "meta: { title: '" + params.title + "' }," : ''}
                children: [],
            }]`, {
                filename: 'file.js',
                plugins: [require('@babel/plugin-syntax-dynamic-import')]
            }) as babel.types.File;

            const element = ((tpl.program.body[0] as babel.types.ExpressionStatement).expression as babel.types.ArrayExpression).elements[0] as babel.types.ObjectExpression;
            parentArray.elements.push(element);
            changed = true;
        }
    }
    if (changed)
        await jsFile.save();

    return;
}

export async function addBranchView(parent: vfs.View, params: AddParams): Promise<string>;
export async function addBranchView(parent: vfs.View, baseView: vfs.View, params: AddParams): Promise<string>;
export async function addBranchView(parentInfo: ViewInfo, baseViewInfo: ViewInfo, params: AddParams): Promise<string>;
export async function addBranchView(parentInfo: ViewInfo | vfs.View, baseViewInfo: ViewInfo | vfs.View | AddParams, params?: AddParams) {
    let parent: vfs.View;
    let baseView: vfs.View;
    if (!params) {
        parent = parentInfo as vfs.View;
        params = baseViewInfo as AddParams;
        baseView = parent;
        while (baseView && baseView.viewType !== vfs.ViewType.entry)
            baseView = baseView.parent;
        if (!baseView)
            return;
    } else {
        parent = parentInfo instanceof vfs.View ? parentInfo : await initView(parentInfo);
        baseView = baseViewInfo instanceof vfs.View ? baseViewInfo : await initView(baseViewInfo as ViewInfo);
        await parent.preOpen();
        await baseView.preOpen();
    }
    params.ext = params.ext || '.vue';

    // parent view 必然是个目录
    const dir = path.join(parent.fullPath, parent.viewsPath, params.name);

    let tplPath;
    if (params.ext === '.vue')
        tplPath = path.resolve(__dirname, '../../templates/branch-view');
    else if (params.ext === '.md')
        tplPath = path.resolve(__dirname, '../../templates/branch-view-md');
    await fs.copy(tplPath, dir);

    const dest = path.join(dir, 'index' + params.ext);
    if (params.layout)
        await initViewLayout(dest, params.layout);

    if (baseView) {
        await addBranchViewRoute(parent, baseView, params);

        if (hasNewParams(params)) {
            await saveMetaData({
                fullPath: dest,
                viewType: vfs.ViewType.branch,
                routePath: parent.routePath + params.name + '/',
            }, params, baseView);
        } else {
            await ensureHotReload(path.join(baseView.fullPath, 'routes.map.js'));
        }
    }
    return dest;
}

export async function addBranchWrapper(parent: vfs.View, params: AddParams): Promise<string>;
export async function addBranchWrapper(parent: vfs.View, baseView: vfs.View, params: AddParams): Promise<string>;
export async function addBranchWrapper(parentInfo: ViewInfo, baseViewInfo: ViewInfo, params: AddParams): Promise<string>;
export async function addBranchWrapper(parentInfo: ViewInfo | vfs.View, baseViewInfo: ViewInfo | vfs.View | AddParams, params?: AddParams) {
    let parent: vfs.View;
    let baseView: vfs.View;
    if (!params) {
        parent = parentInfo as vfs.View;
        params = baseViewInfo as AddParams;
        baseView = parent;
        while (baseView && baseView.viewType !== vfs.ViewType.entry)
            baseView = baseView.parent;
        if (!baseView)
            return;
    } else {
        parent = parentInfo instanceof vfs.View ? parentInfo : await initView(parentInfo);
        baseView = baseViewInfo instanceof vfs.View ? baseViewInfo : await initView(baseViewInfo as ViewInfo);
        await parent.preOpen();
        await baseView.preOpen();
    }
    params.ext = params.ext || '.vue';

    // parent view 必然是个目录
    const dir = path.join(parent.fullPath, parent.viewsPath, params.name);

    const tplPath = path.resolve(__dirname, '../../templates/branch-view');
    await fs.copy(tplPath, dir);

    let dest = path.join(dir, 'index.vue');
    await fs.remove(dest);
    dest = path.dirname(dest);

    const routesPath = path.join(baseView.fullPath, 'routes.js');
    const routesMapPath = path.join(baseView.fullPath, 'routes.map.js');
    if (!fs.existsSync(routesPath) || fs.existsSync(routesMapPath))
        return dest;

    const jsFile = new vfs.JSFile(routesPath);
    await jsFile.open();
    const $js = jsFile.parse();

    let hasImportedLWrapper = false;
    babel.traverse(jsFile.handler.ast, {
        ImportDefaultSpecifier(nodeInfo) {
            if (nodeInfo.node.local.name === 'LWrapper') {
                hasImportedLWrapper = true;
                nodeInfo.stop();
            }
        },
        ImportSpecifier(nodeInfo) {
            if (nodeInfo.node.local.name === 'LWrapper') {
                hasImportedLWrapper = true;
                nodeInfo.stop();
            }
        },
    });
    if (!hasImportedLWrapper) {
        const importDeclaration = babel.template(`import { LWrapper } from 'cloud-ui.vusion'`)() as babel.types.ImportDeclaration;
        jsFile.handler.ast.program.body.unshift(importDeclaration);
    }

    // 纯目录，不带 /index.vue 的
    const relativePath = path.relative(baseView.fullPath, path.join(parent.fullPath, parent.viewsPath, params.name)).replace(/\\/g, '/');
    let changed = false;
    const exportDefault = $js.export().default();
    if (exportDefault.is('object')) {
        const { routeObject, parentArray } = findRouteObjectAndParentArray(exportDefault.node as babel.types.ObjectExpression, relativePath, true);

        if (parentArray && !routeObject) {
            const tpl = babel.parse(`[{
                path: '${params.name}',
                component: LWrapper,
                ${params.title ? "meta: { title: '" + params.title + "' }," : ''}
                children: [],
            }]`, {
                filename: 'file.js',
                plugins: [require('@babel/plugin-syntax-dynamic-import')]
            }) as babel.types.File;

            const element = ((tpl.program.body[0] as babel.types.ExpressionStatement).expression as babel.types.ArrayExpression).elements[0] as babel.types.ObjectExpression;
            parentArray.elements.push(element);
            changed = true;
        }
    }
    if (changed)
        await jsFile.save();

    return dest;
}

/**
 * @TODO remove page metaData
 */
export async function removeView(view: vfs.View): Promise<void>;
export async function removeView(view: vfs.View, baseView: vfs.View): Promise<void>;
export async function removeView(viewInfo: ViewInfo, baseViewInfo: ViewInfo): Promise<void>;
export async function removeView(viewInfo: ViewInfo | vfs.View, baseViewInfo?: ViewInfo | vfs.View) {
    let view: vfs.View;
    let baseView: vfs.View;
    if (!baseViewInfo) {
        view = viewInfo as vfs.View;
        baseView = view;
        while (baseView && baseView.viewType !== vfs.ViewType.entry)
            baseView = baseView.parent;
        if (!baseView)
            return;
    } else {
        view = viewInfo instanceof vfs.View ? viewInfo : await initView(viewInfo);
        baseView = baseViewInfo instanceof vfs.View ? baseViewInfo : await initView(baseViewInfo as ViewInfo);
        await view.preOpen();
        await baseView.preOpen();
    }

    if (baseView) {
        const routesPath = path.join(baseView.fullPath, 'routes.js');
        const routesMapPath = path.join(baseView.fullPath, 'routes.map.js');
        if (fs.existsSync(routesPath) && !fs.existsSync(routesMapPath)) {
            const jsFile = new vfs.JSFile(routesPath);
            await jsFile.open();
            const $js = jsFile.parse();
    
            const relativePath = path.relative(baseView.fullPath, view.fullPath).replace(/\\/g, '/');
            let changed = false;
            const exportDefault = $js.export().default();
            if (exportDefault.is('object')) {
                const { routeObject, parentArray } = findRouteObjectAndParentArray(exportDefault.node as babel.types.ObjectExpression, relativePath, true);
    
                if (routeObject) {
                    parentArray.elements.splice(parentArray.elements.indexOf(routeObject), 1);
    
                    // 判断是不是 LWrapper
                    const LWrapper = routeObject.properties.find((property) => property.type === 'ObjectProperty'
                        && property.key.type === 'Identifier' && property.key.name === 'component'
                        && property.value.type === 'Identifier' && property.value.name === 'LWrapper');
                    if (LWrapper) {
                        let wrapperCount = 0;
                        String(jsFile.content).replace(/LWrapper/, () => String(wrapperCount++));
                        if (wrapperCount === 2) {
                            babel.traverse(jsFile.handler.ast, {
                                ImportDefaultSpecifier(nodeInfo) {
                                    if (nodeInfo.node.local.name === 'LWrapper') {
                                        nodeInfo.remove();
                                        nodeInfo.stop();
                                    }
                                },
                                ImportSpecifier(nodeInfo) {
                                    if (nodeInfo.node.local.name === 'LWrapper') {
                                        nodeInfo.remove();
                                        nodeInfo.stop();
                                    }
                                },
                            });
                        }
                    }
    
                    changed = true;
                }
            }
    
            if (changed)
                await jsFile.save();
        }
    }
    
    await fs.remove(view.fullPath);

    if (baseView)
        await ensureHotReload(path.join(baseView.fullPath, 'routes.map.js'));
}

export interface ParseTypes {
    template?: boolean,
    script?: boolean,
    style?: boolean,
    api?: boolean,
    examples?: boolean,
}

/**
 * 获取服务信息
 */
export async function loadServices(baseViewPath: string) {
     const servicesPath = path.join(baseViewPath, 'services');
     if (!fs.existsSync(servicesPath)) {
        return [];
     }
     const directory = new vfs.Directory(servicesPath);
     await directory.open();
     const tasks = directory.children.filter((item) => item.isDirectory && item.fileName[0] !== '.')
        .map(async (subdir) => {
            const service = new vfs.Service(subdir.fullPath);
            await service.open();
            return service;
        });
     return Promise.all(tasks);
}

/**
 * @deprecated
 * @param fullPath 
 * @param newName 
 * @param name 
 */
export async function addOrRenameService(fullPath: string, newName: string, name: string) {
    if (!name) {
        const dir = path.join(fullPath, 'services', newName);
        let tplPath = path.resolve(__dirname, '../../templates/service');
        await fs.copy(tplPath, dir);
        return path.join(dir, 'api.json');
    } else {
        const oldPath = path.join(fullPath, 'services', name);
        const newPath = path.join(fullPath, 'services', newName);
        await fs.rename(oldPath, newPath);
        return path.join(newPath, 'api.json');
    }
}

export async function saveService(serviceInfo: vfs.Service) {
    const service = new vfs.Service(serviceInfo.fullPath);
    await service.open();
    Object.assign(service, serviceInfo);
    await service.save();
}

export async function removeService(fullPath: string) {
    await fs.remove(fullPath);
    await ensureHotReload(path.join(fullPath, '../index.js'));
}

/**
 * 区块的复杂程度类型
 */
const enum BlockComplexity {
    onlyTemplate, // Just add
    hasScriptOrStyle, // Can merge
    hasAssetsOrExtra, // Must external
}

/**
 * 组件或区块信息
 */
interface BlockInfo {
    name: string,
    title: string,
    tagName: string,
    dependencies: { [name: string]: string },
    vusionDependencies: { [name: string]: string },
    registry: string,
    uuid?: string,
}

/**
 * 替换占位符内容
 * @param fullPath 文件路径
 * @param blockInfo 组件或区块信息
 * @param content 要替换的内容
 */
async function replacePlaceholder(fullPath: string, blockInfo: BlockInfo, content: string | vfs.VueFile){
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.forceOpen();
    vueFile.parseAll();

    let progressArray: any[] = [];
    vueFile.templateHandler.traverse((nodeInfo) => {
        const node = nodeInfo.node as compiler.ASTElement;
        if (node.tag === 'd-progress' && node.attrsMap.uuid === blockInfo.uuid){
            progressArray.push(nodeInfo.route);
            nodeInfo.remove();
        }
    });
    const blockVue = typeof content === 'string' ? vfs.VueFile.from(content) : content;
    blockVue.parseAll();
    progressArray.forEach((route)=>{
        vueFile.merge(blockVue, route);
    });
    await vueFile.save();
}

/**
 * 在有其它代码或 Assets 的情况下，直接添加为外部区块
 */
async function external(fullPath: string, blockInfo: BlockInfo, blockVue: vfs.VueFile) {
    if(!fs.existsSync(path.join(fullPath.replace(/\.vue$/, '.blocks'), blockInfo.tagName + '.vue'))){
        await vms.addBlockExternally(blockVue, fullPath, blockInfo.tagName);
    } else {
        const vueFile = new vfs.VueFile(fullPath);
        await vueFile.open();
        /* 添加 import */
        const relativePath = `./${vueFile.baseName}.blocks/${blockInfo.tagName}.vue`;
        const { componentName } = utils.normalizeName(blockInfo.tagName);
        const $js = vueFile.parseScript();
        
        const components = $js.export().default().object().get('components');
        if (!components || !components.get(componentName)) {
            $js.import(componentName).from(relativePath);
            $js.export().default().object()
                .after(['el','name','parent','functional','delimiters','comments'])
                .ensure('components', '{}')
                .get('components')
                .set(componentName, componentName);

            await vueFile.save();
        }   
    }
    const content = `<template><${blockInfo.tagName}></${blockInfo.tagName}></template>`;
    await replacePlaceholder(fullPath, blockInfo, content);
}

/**
 * 添加区块
 * @param fullPath 文件路径
 * @param libraryPath 全局组件路径，components/index.js所在路径
 * @param blockInfo 组件或区块信息
 * @param tpl 组件代码字符串
 * @param nodePath 节点路径
 */
export async function addBlock(fullPath: string, blockInfo: BlockInfo){
    const options = {
        source: {
            type: 'file',
            registry: blockInfo.registry,
            name: blockInfo.name,
            fileName: blockInfo.tagName + '.vue',
            baseName: blockInfo.tagName,
        },
        target: fullPath,
        name: blockInfo.tagName,
    };
    const blockPath = await vms.fetchBlock(options);
    let blockVue: vfs.VueFile;
    blockVue = new vfs.VueFile(blockPath.replace(/\.vue@.+$/, '.vue'));
    blockVue.fullPath = blockPath;
    await blockVue.open();

    // 区块的复杂程度
    let blockComplexity: BlockComplexity;
    if (blockVue.hasAssets() || blockVue.hasExtra())
        blockComplexity = BlockComplexity.hasAssetsOrExtra;
    else if (blockVue.hasScript(true) || blockVue.hasStyle(true))
        blockComplexity = BlockComplexity.hasScriptOrStyle;
    else
        blockComplexity = BlockComplexity.onlyTemplate;

    if (blockComplexity === BlockComplexity.hasAssetsOrExtra) {
        return await external(fullPath, blockInfo, blockVue);
    }else{
        return await replacePlaceholder(fullPath, blockInfo, blockVue);
    }
}

/**
 * 添加业务组件
 * @param fullPath 文件路径
 * @param libraryPath 全局组件路径，components/index.js所在路径
 * @param blockInfo 组件或区块信息
 * @param tpl 组件代码字符串
 * @param nodePath 节点路径
 */
export async function addCustomComponent(fullPath: string, libraryPath: string, blockInfo: BlockInfo, content: string) {
    const library = new vfs.Library(libraryPath, vfs.LibraryType.internal);
    await library.open();
    const indexFile = library.componentsIndexFile;
    if(indexFile){
        await indexFile.forceOpen();
        const $js = indexFile.parse();
        $js.export('*').from(blockInfo.name);
        await indexFile.save();
    }

    await replacePlaceholder(fullPath, blockInfo, content);
}

export async function loadPackageJSON(rootPath: string) {
    const pkgPath = path.resolve(rootPath, 'package.json');
    if(!fs.existsSync(pkgPath))
        return {};
    return JSON.parse(await fs.readFile(pkgPath, 'utf8'));
}

export async function loadExternalLibrary(fullPath: string, parseTypes: ParseTypes = {}) {
    const library = new vfs.Library(fullPath, vfs.LibraryType.external);
    await library.open();
    await Promise.all(library.components.map(async (vueFile) => {
        await vueFile.open();
        if (parseTypes.template)
            vueFile.parseTemplate();
        if (parseTypes.script)
            vueFile.parseScript();
        if (parseTypes.style)
            vueFile.parseStyle();
        if (parseTypes.api)
            vueFile.parseAPI();
        if (parseTypes.examples)
            vueFile.parseExamples();
    }));
    return library;
}

/**
 * 获取单个控件信息
 * @param fullPath 控件路径
 * @param parseTypes 需要获取的信息
 */
export async function loadComponentData(fullPath: string, parseTypes: ParseTypes = {}){
    if(!fs.existsSync(fullPath))
        return {};
    const vueFile = new vfs.VueFile(fullPath);
    await vueFile.open();
    if (parseTypes.template)
        vueFile.parseTemplate();
    if (parseTypes.script)
        vueFile.parseScript();
    if (parseTypes.style)
        vueFile.parseStyle();
    if (parseTypes.api)
        vueFile.parseAPI();
    if (parseTypes.examples)
        vueFile.parseExamples();
    return vueFile;
}

/**
 * 获取自定义组件信息，packages.json中有的组件，并且是以.vue结尾
 * @param rootPath package.json所在的目录路径
 * @param parseTypes 需要获取的信息
 * @param baseName 组件信息，有该信息则获取该组件信息
 */
export async function loadCustomComponentsData(rootPath: string, parseTypes: ParseTypes = {}, baseName?: string){
    const pkg = await loadPackageJSON(rootPath);
    const tasks: Array<Promise<any>> = [];

    Object.keys(pkg.dependencies || {}).filter((name) => {
        if(baseName)
            return name.includes(baseName + '.vue');
        else
            return name.endsWith('.vue');
    }).forEach((name) => {
        tasks.push(loadComponentData(`${rootPath}/node_modules/${name}`, parseTypes));
    });

    Object.keys(pkg.vusionDependencies || {}).filter((name) => {
        if(baseName)
            return name.includes(baseName + '.vue');
        else
            return name.endsWith('.vue');
    }).forEach((name) => {
        tasks.push(loadComponentData(`${rootPath}/vusion_packages/${name}`, parseTypes));
    });
    
    return Promise.all(tasks);
}

export async function addAuthCache(name: string, filePath: string) {
    await fs.ensureFile(filePath);

    let json: any = {};
    try {
        json = JSON.parse(await fs.readFile(filePath, 'utf8'));
    } catch(e) {}

    json[name] = true;
    await fs.writeFile(filePath, JSON.stringify(json, null, 4));
}

export async function removeAuthCache(name: string, filePath: string) {
    await fs.ensureFile(filePath);

    let json: any = {};
    try {
        json = JSON.parse(await fs.readFile(filePath, 'utf8'));
    } catch(e) {}

    delete json[name];
    await fs.writeFile(filePath, JSON.stringify(json, null, 4));
}

export async function loadAuthCache(filePath: string) {
    try {
        return JSON.parse(await fs.readFile(filePath, 'utf8'));
    } catch(e) {
        return {};
    }
}