'use strict';

var _es6Map = require('es6-map');

var _es6Map2 = _interopRequireDefault(_es6Map);

var _getExports = require('../core/getExports');

var _getExports2 = _interopRequireDefault(_getExports);

var _importDeclaration = require('../importDeclaration');

var _importDeclaration2 = _interopRequireDefault(_importDeclaration);

var _declaredScope = require('../core/declaredScope');

var _declaredScope2 = _interopRequireDefault(_declaredScope);

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

exports.meta = {
  schema: [{
    'type': 'object',
    'properties': {
      'allowComputed': {
        'description': 'If `false`, will report computed (and thus, un-lintable) references ' + 'to namespace members.',
        'type': 'boolean',
        'default': false
      }
    },
    'additionalProperties': false
  }]
};

exports.create = function namespaceRule(context) {

  // read options
  var _ref = context.options[0] || {};

  var _ref$allowComputed = _ref.allowComputed;
  var allowComputed = _ref$allowComputed === undefined ? false : _ref$allowComputed;


  var namespaces = new _es6Map2.default();

  function makeMessage(last, namepath) {
    return '\'' + last.name + '\' not found in' + (namepath.length > 1 ? ' deeply ' : ' ') + ('imported namespace \'' + namepath.join('.') + '\'.');
  }

  return {

    // pick up all imports at body entry time, to properly respect hoisting
    'Program': function Program(_ref2) {
      var body = _ref2.body;

      function processBodyStatement(declaration) {
        if (declaration.type !== 'ImportDeclaration') return;

        if (declaration.specifiers.length === 0) return;

        var imports = _getExports2.default.get(declaration.source.value, context);
        if (imports == null) return null;

        if (imports.errors.length) {
          imports.reportErrors(context, declaration);
          return;
        }

        declaration.specifiers.forEach(function (specifier) {
          switch (specifier.type) {
            case 'ImportNamespaceSpecifier':
              if (!imports.size) {
                context.report(specifier, 'No exported names found in module \'' + declaration.source.value + '\'.');
              }
              namespaces.set(specifier.local.name, imports);
              break;
            case 'ImportDefaultSpecifier':
            case 'ImportSpecifier':
              {
                var meta = imports.get(
                // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg
                specifier.imported ? specifier.imported.name : 'default');
                if (!meta || !meta.namespace) break;
                namespaces.set(specifier.local.name, meta.namespace);
                break;
              }
          }
        });
      }
      body.forEach(processBodyStatement);
    },

    // same as above, but does not add names to local map
    'ExportNamespaceSpecifier': function ExportNamespaceSpecifier(namespace) {
      var declaration = (0, _importDeclaration2.default)(context);

      var imports = _getExports2.default.get(declaration.source.value, context);
      if (imports == null) return null;

      if (imports.errors.length) {
        imports.reportErrors(context, declaration);
        return;
      }

      if (!imports.size) {
        context.report(namespace, 'No exported names found in module \'' + declaration.source.value + '\'.');
      }
    },

    // todo: check for possible redefinition

    'MemberExpression': function MemberExpression(dereference) {
      if (dereference.object.type !== 'Identifier') return;
      if (!namespaces.has(dereference.object.name)) return;

      if (dereference.parent.type === 'AssignmentExpression' && dereference.parent.left === dereference) {
        context.report(dereference.parent, 'Assignment to member of namespace \'' + dereference.object.name + '\'.');
      }

      // go deep
      var namespace = namespaces.get(dereference.object.name);
      var namepath = [dereference.object.name];
      // while property is namespace and parent is member expression, keep validating
      while (namespace instanceof _getExports2.default && dereference.type === 'MemberExpression') {

        if (dereference.computed) {
          if (!allowComputed) {
            context.report(dereference.property, 'Unable to validate computed reference to imported namespace \'' + dereference.object.name + '\'.');
          }
          return;
        }

        if (!namespace.has(dereference.property.name)) {
          context.report(dereference.property, makeMessage(dereference.property, namepath));
          break;
        }

        var exported = namespace.get(dereference.property.name);
        if (exported == null) return;

        // stash and pop
        namepath.push(dereference.property.name);
        namespace = exported.namespace;
        dereference = dereference.parent;
      }
    },

    'VariableDeclarator': function VariableDeclarator(_ref3) {
      var id = _ref3.id;
      var init = _ref3.init;

      if (init == null) return;
      if (init.type !== 'Identifier') return;
      if (!namespaces.has(init.name)) return;

      // check for redefinition in intermediate scopes
      if ((0, _declaredScope2.default)(context, init.name) !== 'module') return;

      // DFS traverse child namespaces
      function testKey(pattern, namespace) {
        var path = arguments.length <= 2 || arguments[2] === undefined ? [init.name] : arguments[2];

        if (!(namespace instanceof _getExports2.default)) return;

        if (pattern.type !== 'ObjectPattern') return;

        pattern.properties.forEach(function (property) {
          if (property.key.type !== 'Identifier') {
            context.report({
              node: property,
              message: 'Only destructure top-level names.'
            });
          } else if (!namespace.has(property.key.name)) {
            context.report({
              node: property,
              message: makeMessage(property.key, path)
            });
          } else {
            path.push(property.key.name);
            testKey(property.value, namespace.get(property.key.name).namespace, path);
            path.pop();
          }
        });
      }

      testKey(id, namespaces.get(init.name));
    }
  };
};
//# sourceMappingURL=data:application/json;base64,[encoded data, 14164 characters]