import esutils from "esutils";
import * as t from "./index";

/**
 * Check if the input `node` is a reference to a bound variable.
 */

export function isReferenced(node: Object, parent: Object): boolean {
  switch (parent.type) {
    // yes: PARENT[NODE]
    // yes: NODE.child
    // no: parent.CHILD
    case "MemberExpression":
      if (parent.property === node && parent.computed) {
        return true;
      } else if (parent.object === node) {
        return true;
      } else {
        return false;
      }

    // no: new.NODE
    // no: NODE.target
    case "MetaProperty":
      return false;

    // yes: { [NODE]: "" }
    // yes: { NODE }
    // no: { NODE: "" }
    case "Property":
      if (parent.key === node) {
        return parent.computed;
      }

    // no: var NODE = init;
    // yes: var id = NODE;
    case "VariableDeclarator":
      return parent.id !== node;

    // no: function NODE() {}
    // no: function foo(NODE) {}
    case "ArrowFunctionExpression":
    case "FunctionDeclaration":
    case "FunctionExpression":
      for (var param of (parent.params: Array)) {
        if (param === node) return false;
      }

      return parent.id !== node;

    // no: export { foo as NODE };
    case "ExportSpecifier":
      return parent.exported !== node;

    // no: import { NODE as foo } from "foo";
    case "ImportSpecifier":
      return parent.imported !== node;

    // no: class NODE {}
    case "ClassDeclaration":
    case "ClassExpression":
      return parent.id !== node;

    // yes: class { [NODE](){} }
    case "MethodDefinition":
      return parent.key === node && parent.computed;

    // no: NODE: for (;;) {}
    case "LabeledStatement":
      return false;

    // no: try {} catch (NODE) {}
    case "CatchClause":
      return parent.param !== node;

    // no: function foo(...NODE) {}
    case "RestElement":
      return false;

    // no: [NODE = foo] = [];
    // yes: [foo = NODE] = [];
    case "AssignmentPattern":
      return parent.right === node;

    // no: [NODE] = [];
    // no: ({ NODE }) = [];
    case "ObjectPattern":
    case "ArrayPattern":
      return false;

    // no: import NODE from "bar";
    case "ImportSpecifier":
      return false;

    // no: import * as NODE from "foo";
    case "ImportNamespaceSpecifier":
      return false;
  }

  return true;
}

/**
 * Check if the input `node` is an `Identifier` and `isReferenced`.
 */

export function isReferencedIdentifier(node: Object, parent: Object, opts?: Object): boolean {
  return (t.isIdentifier(node, opts) || t.isJSXIdentifier(node, opts)) && t.isReferenced(node, parent);
}

/**
 * Check if the input `name` is a valid identifier name
 * and isn't a reserved word.
 */

export function isValidIdentifier(name: string): boolean {
  if (typeof name !== "string" || esutils.keyword.isReservedWordES6(name, true)) {
    return false;
  } else {
    return esutils.keyword.isIdentifierNameES6(name);
  }
}

/**
 * Description
 */

export function isLet(node: Object): boolean {
  return t.isVariableDeclaration(node) && (node.kind !== "var" || node._let);
}

/**
 * Description
 */

export function isBlockScoped(node: Object): boolean {
  return t.isFunctionDeclaration(node) || t.isClassDeclaration(node) || t.isLet(node);
}

/**
 * Description
 */

export function isVar(node: Object): boolean {
  return t.isVariableDeclaration(node, { kind: "var" }) && !node._let;
}

/**
 * Description
 */

export function isSpecifierDefault(specifier: Object): boolean {
  return t.isImportDefaultSpecifier(specifier) ||
         t.isExportDefaultSpecifier(specifier) ||
         t.isIdentifier(specifier.imported || specifier.exported, { name: "default" });
}

/**
 * Description
 */

export function isScope(node: Object, parent: Object): boolean {
  if (t.isBlockStatement(node) && t.isFunction(parent, { body: node })) {
    return false;
  }

  return t.isScopable(node);
}

/**
 * Description
 */

export function isImmutable(node: Object): boolean {
  if (t.isType(node.type, "Immutable")) return true;

  if (t.isLiteral(node)) {
    if (node.regex) {
      // regexes are mutable
      return false;
    } else {
      // immutable!
      return true;
    }
  } else if (t.isIdentifier(node)) {
    if (node.name === "undefined") {
      // immutable!
      return true;
    } else {
      // no idea...
      return false;
    }
  }

  return false;
}

@@ -1229,32 +1229,180 @@
     
-case %22ExportSpecifier%22:%0A
+// yes: export %7B NODE as foo %7D;%0A    // no: export %7B NODE as foo %7D from %22foo%22;%0A    case %22ExportSpecifier%22:%0A      if (parent.source) %7B%0A        return false;%0A      %7D else %7B%0A  
     
@@ -1421,26 +1421,23 @@
 ent.
-exported !
+local =
 == node;
 %0A%0A  
@@ -1424,32 +1424,40 @@
 .local === node;
+%0A      %7D
 %0A%0A    // no: imp
@@ -1459,32 +1459,117 @@
 : import
- %7B
  NODE 
-as foo %7D
+from %22foo%22;%0A    case %22ImportDefaultSpecifier%22:%0A      return false;%0A%0A    // no: import * as NODE
  from %22f
@@ -1581,32 +1581,41 @@
     case %22Import
+Namespace
 Specifier%22:%0A    
@@ -1627,31 +1627,153 @@
 urn 
-parent.imported !== nod
+false;%0A%0A    // no: import %7B NODE as foo %7D from %22foo%22;%0A    // no: import %7B foo as NODE %7D from %22foo%22;%0A    case %22ImportSpecifier%22:%0A      return fals
 e;%0A%0A

fix and add missing module specifier reference detection - fixes #1520