const targetCalleeName = ['log', 'info'].map(i => `console.${i}`)
const _pluginSyntaxJsx = require('@babel/plugin-syntax-jsx')
const _core = require('@babel/core')
const _helperModuleImports = require('@babel/helper-module-imports')

const PluginName = '@babel/plugin-test'

const defaultConfig = {
  moduleName: './Dream',
  pragma: 'Dream.createElement'
}

const JSX_SOURCE_ANNOTATION_REGEX = /^\s*\*?\s*@jsxImportSource\s+([^\s]+)\s*$/m;
const JSX_RUNTIME_ANNOTATION_REGEX = /^\s*\*?\s*@jsxRuntime\s+([^\s]+)\s*$/m;
const JSX_ANNOTATION_REGEX = /^\s*\*?\s*@jsx\s+([^\s]+)\s*$/m;

const mapIns = new Map()
const set = (k, v, map=mapIns) => map.set(`${PluginName}/${k}`, v)
const get = (k, map=mapIns) => map.get(`${PluginName}/${k}`)

function toMemberExpression(id) {
  return id.split(".").map(name => _core.types.identifier(name)).reduce((object, property) => _core.types.memberExpression(object, property));
}

function call(pass, name, args) {
  const node = _core.types.callExpression(get(`id/${name}`, pass)(), args);

  // if (PURE_ANNOTATION != null ? PURE_ANNOTATION : get(pass, "defaultPure")) (0, _helperAnnotateAsPure.default)(node);
  return node;
}

/**
 * 转换 attribute 的value
 * @param {*} node 
 * @returns 
 */
function convertAttributeValue(node) {
  if (_core.types.isJSXExpressionContainer(node)) {
    return node.expression;
  } else {
    return node;
  }
}

function accumulateAttribute(array, attribute) {
  if (_core.types.isJSXSpreadAttribute(attribute.node)) {
    const arg = attribute.node.argument;

    if (_core.types.isObjectExpression(arg)) {
      array.push(...arg.properties);
    } else {
      array.push(_core.types.spreadElement(arg));
    }

    return array;
  }

  const value = convertAttributeValue(attribute.node.name.name !== "key" ? attribute.node.value || _core.types.booleanLiteral(true) : attribute.node.value);

  if (attribute.node.name.name === "key" && value === null) {
    throw attribute.buildCodeFrameError('Please provide an explicit key value. Using "key" as a shorthand for "key={true}" is not allowed.');
  }

  if (_core.types.isStringLiteral(value) && !_core.types.isJSXExpressionContainer(attribute.node.value)) {
    var _value$extra;

    value.value = value.value.replace(/\n\s+/g, " ");
    (_value$extra = value.extra) == null ? true : delete _value$extra.raw;
  }

  // 是否是 jsx NameSpaceName
  if (_core.types.isJSXNamespacedName(attribute.node.name)) {
    attribute.node.name = _core.types.stringLiteral(attribute.node.name.namespace.name + ":" + attribute.node.name.name.name);
  } else if (_core.types.isValidIdentifier(attribute.node.name.name, false)) {
    attribute.node.name.type = "Identifier";
  } else {
    attribute.node.name = _core.types.stringLiteral(attribute.node.name.name);
  }

  let t = _core.types.inherits(_core.types.objectProperty(attribute.node.name, value), attribute.node)
  array.push(t);
  return array;
}

function buildCreateElementOpeningElementAttributes(file, path, attribs) {
  const objs = [];
  const props = attribs.reduce(accumulateAttribute, []);

  if (!defaultConfig.useSpread) {
    let start = 0;
    props.forEach((prop, i) => {
      if (_core.types.isSpreadElement(prop)) {
        if (i > start) {
          objs.push(_core.types.objectExpression(props.slice(start, i)));
        }

        objs.push(prop.argument);
        start = i + 1;
      }
    });

    if (props.length > start) {
      objs.push(_core.types.objectExpression(props.slice(start)));
    }
  } else if (props.length) {
    objs.push(_core.types.objectExpression(props));
  }

  if (!objs.length) {
    return _core.types.nullLiteral();
  }

  if (objs.length === 1) {
    return objs[0];
  }

  if (!_core.types.isObjectExpression(objs[0])) {
    objs.unshift(_core.types.objectExpression([]));
  }

  const helper = defaultConfig.useBuiltIns ? _core.types.memberExpression(_core.types.identifier("Object"), _core.types.identifier("assign")) : file.addHelper("extends");
  return _core.types.callExpression(helper, objs);
}

/**
 * 获取标签名称
 * @param {*} openingPath 
 * @returns 
 */
function getTag(openingPath) {
  const tagExpr = convertJSXIdentifier(openingPath.node.name, openingPath.node);
  let tagName;

  if (_core.types.isIdentifier(tagExpr)) {
    tagName = tagExpr.name;
  } else if (_core.types.isLiteral(tagExpr)) {
    tagName = tagExpr.value;
  }
  // isCompatTag /^[a-z]*/.test(tagName)
  if (_core.types.react.isCompatTag(tagName)) {
    return _core.types.stringLiteral(tagName);
  } else {
    return tagExpr;
  }
}

function buildCreateElementCall(path, file) {
  const openingPath = path.get("openingElement");
  return call(file, "createElement", [getTag(openingPath),buildCreateElementOpeningElementAttributes(file, path, openingPath.get("attributes")), ..._core.types.react.buildChildren(path.node)]);
}

function convertJSXIdentifier(node, parent) {
  if (_core.types.isJSXIdentifier(node)) {
    // 
    if (node.name === "this" && _core.types.isReferenced(node, parent)) {
      return _core.types.thisExpression();
    } else if (_core.types.isValidIdentifier(node.name, false)) {
      node.type = "Identifier";
    } else {
      return _core.types.stringLiteral(node.name);
    }
  } else if (_core.types.isJSXMemberExpression(node)) {
    return _core.types.memberExpression(convertJSXIdentifier(node.object, node), convertJSXIdentifier(node.property, node));
  } else if (_core.types.isJSXNamespacedName(node)) {
    return _core.types.stringLiteral(`${node.namespace.name}:${node.name.name}`);
  }

  return node;
}

function createImportLazily(pass, path, importName, source) {
  return () => {
    const actualSource = source // getSource(source, importName);

    if ((_helperModuleImports.isModule)(path)) {
      let reference = get(`imports/${importName}`, pass);
      if (reference) return _core.types.cloneNode(reference);
      // reference = _helperModuleImports.addNamed(path, importName, actualSource, {
      //   importedInterop: "uncompiled",
      //   importPosition: "after"
      // })
      reference = _helperModuleImports.addDefault(path, actualSource, {
        nameHint: importName
      })
      set(`imports/${importName}`, reference, pass);
      return reference;
    } else {
      let reference = get(`requires/${actualSource}`, pass);

      if (reference) {
        reference = _core.types.cloneNode(reference);
      } else {
        reference = _helperModuleImports.addNamespace(path, actualSource, {
          importedInterop: "uncompiled"
        })
        set(`requires/${actualSource}`, reference, pass)
      }
      return _core.types.memberExpression(reference, _core.types.identifier(importName));
    }
  };
}

module.exports = function(api, options) {
  const {
    types, template,
    generate
  } = api;
  return {
    inherits: _pluginSyntaxJsx.default,
    visitor: {
      Program: {
        enter(path, state) {
          const {
            file
          } = state;
          let runtime = 'development'
          let source = defaultConfig.moduleName
          let pragma = defaultConfig.pragma

          // if (file.ast.comments) {
          //   for (const comment of file.ast.comments) {
          //     const sourceMatches = JSX_SOURCE_ANNOTATION_REGEX.exec(comment.value);

          //     if (sourceMatches) {
          //       source = sourceMatches[1];
          //     }

          //     const runtimeMatches = JSX_RUNTIME_ANNOTATION_REGEX.exec(comment.value);

          //     if (runtimeMatches) {
          //       runtime = runtimeMatches[1];
          //     }

          //     const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);

          //     if (jsxMatches) {
          //       pragma = jsxMatches[1];
          //     }
          //   }
          // }

          // path.traverse({
          //   ImportDeclaration(curPath) {
          //     const requirePath = curPath.get('source').node.value;
          //     if (requirePath === options.trackerPath) {// 如果已经引入了
          //       const specifierPath = curPath.get('specifiers.0');
          //       if (specifierPath.isImportSpecifier()) { 
          //           state.trackerImportId = specifierPath.toString();
          //       } else if(specifierPath.isImportNamespaceSpecifier()) {
          //           state.trackerImportId = specifierPath.get('local').toString();// tracker 模块的 id
          //       }
          //       path.stop();// 找到了就终止遍历
          //     }
          //   }
          // })

          const createElement = toMemberExpression(pragma);
          set("id/createElement", () => _core.types.cloneNode(createElement), state);
          // createImportLazily(state, path, 'Dream', defaultConfig.moduleName)()
        }
      },
      JSXSpreadChild(path) {
        throw path.buildCodeFrameError("Spread children are not supported in React.");
      },
      // CallExpression(path, state) {
      //   const node = path.node
      //   if (node.isSkip) return
      //   const calleeName = path.get('callee').toString()
      //   if (targetCalleeName.includes(calleeName)) {
      //     const { line, column } = node.loc.start
      //     // node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
      //     const newNode = template.expression(`console.log('%c%s loc: %s', 'color: green', '${state.filename}', %%FILEPOSITION%%)`)({
      //       // DATE: 'new Date().toLocaleString()',//'new Date().toLocaleString()',
      //       FILEPOSITION: `'(${line}, ${column})'`
      //     })
    
      //     if (path.findParent(path => path.isJSXElement())) {
      //       path.replaceWith(types.arrayExpression([newNode, node]))
      //       path.skip()
      //     } else {
      //       path.insertBefore(newNode)
      //     }
    
      //     newNode.isSkip = true
      //     node.isSkip = true
      //   }
      // },
      JSXElement: {
        exit(path, state) {

          let callExpr = buildCreateElementCall(path, state);
          // inherits _core.types.inherits(callExpr, path.node)
          path.replaceWith(callExpr);
        }
      }
    }
  }
}