"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = writeNewRoute;
exports.getNewRouteCode = getNewRouteCode;
exports.writeRouteNode = writeRouteNode;

function _react() {
  const data = _interopRequireDefault(require("react"));

  _react = function _react() {
    return data;
  };

  return data;
}

function _fs() {
  const data = require("fs");

  _fs = function _fs() {
    return data;
  };

  return data;
}

function _path() {
  const data = require("path");

  _path = function _path() {
    return data;
  };

  return data;
}

function parser() {
  const data = _interopRequireWildcard(require("@babel/parser"));

  parser = function parser() {
    return data;
  };

  return data;
}

function _traverse() {
  const data = _interopRequireDefault(require("@babel/traverse"));

  _traverse = function _traverse() {
    return data;
  };

  return data;
}

function _generator() {
  const data = _interopRequireDefault(require("@babel/generator"));

  _generator = function _generator() {
    return data;
  };

  return data;
}

function t() {
  const data = _interopRequireWildcard(require("@babel/types"));

  t = function t() {
    return data;
  };

  return data;
}

function _umiUtils() {
  const data = require("umi-utils");

  _umiUtils = function _umiUtils() {
    return data;
  };

  return data;
}

function _prettier() {
  const data = _interopRequireDefault(require("prettier"));

  _prettier = function _prettier() {
    return data;
  };

  return data;
}

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const debug = require('debug')('umi-build-dev:writeNewRoute');
/**
 * 将路由写入路由文件
 * @param {*} newRoute 新的路由配置: { path, component, ... }
 * @param {*} configPath 配置路径
 * @param {*} absSrcPath 代码路径
 */


function writeNewRoute(newRoute, configPath, absSrcPath) {
  const _getNewRouteCode = getNewRouteCode(configPath, newRoute, absSrcPath),
        code = _getNewRouteCode.code,
        routesPath = _getNewRouteCode.routesPath;

  (0, _fs().writeFileSync)(routesPath, code, 'utf-8');
}
/**
 * 获取目标
 * @param {*} configPath
 * @param {*} newRoute
 */


function getNewRouteCode(configPath, newRoute, absSrcPath) {
  debug(`find routes in configPath: ${configPath}`);
  const ast = parser().parse((0, _fs().readFileSync)(configPath, 'utf-8'), {
    sourceType: 'module',
    plugins: ['typescript']
  });
  let routesNode = null;
  const importModules = []; // 查询当前配置文件是否导出 routes 属性

  (0, _traverse().default)(ast, {
    Program({
      node
    }) {
      // find import
      const body = node.body;
      body.forEach(item => {
        if (t().isImportDeclaration(item)) {
          const specifiers = item.specifiers;
          const defaultEpecifier = specifiers.find(s => {
            return t().isImportDefaultSpecifier(s) && t().isIdentifier(s.local);
          });

          if (defaultEpecifier && t().isStringLiteral(item.source)) {
            importModules.push({
              identifierName: defaultEpecifier.local.name,
              modulePath: item.source.value
            });
          }
        }
      });
    },

    AssignmentExpression({
      node
    }) {
      // for exports.routes
      const left = node.left,
            operator = node.operator,
            right = node.right;

      if (operator === '=' && t().isMemberExpression(left) && t().isIdentifier(left.object) && left.object.name === 'exports' && t().isIdentifier(left.property) && left.property.name === 'routes') {
        routesNode = right;
      }
    },

    ExportDefaultDeclaration({
      node
    }) {
      // export default []
      const declaration = node.declaration;

      if (t().isArrayExpression(declaration)) {
        routesNode = declaration;
      }
    },

    ObjectExpression({
      node,
      parent
    }) {
      // find routes on object, like { routes: [] }
      if (t().isArrayExpression(parent)) {
        // children routes
        return;
      }

      const properties = node.properties;
      properties.forEach(p => {
        const key = p.key,
              value = p.value;

        if (t().isObjectProperty(p) && t().isIdentifier(key) && key.name === 'routes') {
          routesNode = value;
        }
      });
    }

  });

  if (routesNode) {
    // routes 配置不在当前文件, 需要 load 对应的文件  export default { routes: pageRoutes } case 1
    if (!t().isArrayExpression(routesNode)) {
      const source = importModules.find(m => {
        return m.identifierName === routesNode.name;
      });

      if (source) {
        const newConfigPath = getModulePath(configPath, source.modulePath, absSrcPath);
        return getNewRouteCode(newConfigPath, newRoute, absSrcPath);
      } else {
        throw new Error(`can not find import of ${routesNode.name}`);
      }
    } else {
      // 配置在当前文件 // export default { routes: [] } case 2
      writeRouteNode(routesNode, newRoute);
    }
  } else {
    throw new Error('route array config not found.');
  }

  const code = generateCode(ast);
  debug(code, configPath);
  return {
    code,
    routesPath: configPath
  };
}

function getNewRouteNode(newRoute) {
  return parser().parse(`(${JSON.stringify(newRoute)})`).program.body[0].expression;
}
/**
 * 写入节点
 * @param {*} node 找到的节点
 * @param {*} newRoute 新的路由配置
 */


function writeRouteNode(targetNode, newRoute, currentPath = '/') {
  debug(`writeRouteNode currentPath newRoute.path: ${newRoute.path} currentPath: ${currentPath}`);
  const elements = targetNode.elements;
  const paths = elements.map(ele => {
    if (!t().isObjectExpression(ele)) {
      return false;
    }

    const properties = ele.properties;
    const redirect = properties.find(p => {
      return p.key.name === 'redirect';
    });

    if (redirect) {
      return false;
    }

    const pathProp = properties.find(p => {
      return p.key.name === 'path';
    });

    if (!pathProp) {
      return currentPath;
    }

    let fullPath = pathProp.value.value;

    if (fullPath.indexOf('/') !== 0) {
      fullPath = (0, _path().join)(currentPath, fullPath);
    }

    return fullPath;
  });
  debug('paths', paths);
  const matchedIndex = paths.findIndex(p => {
    return p && newRoute.path.indexOf((0, _umiUtils().winPath)(p)) === 0;
  });
  const newNode = getNewRouteNode(newRoute);

  if (matchedIndex === -1) {
    elements.push(newNode); // return container for test

    return targetNode;
  } else {
    // matched, insert to children routes
    const matchedEle = elements[matchedIndex];
    const routesProp = matchedEle.properties.find(p => {
      return p.key.name === 'routes' || process.env.BIGFISH_COMPAT && p.key.name === 'childRoutes';
    });

    if (!routesProp) {
      // not find children routes, insert before the matched element
      elements.splice(matchedIndex, 0, newNode);
      return targetNode;
    }

    return writeRouteNode(routesProp.value, newRoute, paths[matchedIndex]);
  }
}
/**
 * 生成代码
 * @param {*} ast
 */


function generateCode(ast) {
  const newCode = (0, _generator().default)(ast, {}).code;
  return _prettier().default.format(newCode, {
    // format same as ant-design-pro
    singleQuote: true,
    trailingComma: 'es5',
    printWidth: 100,
    parser: 'typescript'
  });
}
/**
 * 获取路由配置的真实路径
 * @param {*} modulePath
 */


function getModulePath(configPath, modulePath, absSrcPath) {
  // like @/route.config
  if (/^@\//.test(modulePath)) {
    modulePath = (0, _path().join)(absSrcPath, modulePath.replace(/^@\//, ''));
  } else {
    modulePath = (0, _path().join)((0, _path().dirname)(configPath), modulePath);
  }

  if (!/\.[j|t]s$/.test(modulePath)) {
    if ((0, _fs().existsSync)(`${modulePath}.js`)) {
      modulePath = `${modulePath}.js`;
    } else {
      modulePath = `${modulePath}.ts`;
    }
  }

  return modulePath;
}