import parse from '../../src/parsers/javascript.js';
import removePosition from '../../src/remark-remove-position.js';
import { remark } from 'remark';
const remarkParse = remark().use(removePosition).parse;
import { visit } from 'unist-util-visit';

function pick(obj, props) {
  if (Array.isArray(props)) {
    return props.reduce(function (memo, prop) {
      if (obj[prop] !== undefined) {
        memo[prop] = obj[prop];
      }
      return memo;
    }, {});
  }
  return obj[props];
}

function evaluate(fn, filename) {
  return parse(
    {
      file: filename || 'test.js',
      source: fn instanceof Function ? '(' + fn.toString() + ')' : fn
    },
    {}
  );
}

function addJSDocTag(tree) {
  visit(tree, 'link', function (node) {
    node.jsdoc = true;
  });
  return tree;
}

test('parse - @abstract', function () {
  expect(
    evaluate(function () {
      /** @abstract */
    })[0].abstract
  ).toBe(true);
});

test('parse - @access', function () {
  expect(
    evaluate(function () {
      /** @access public */
    })[0].access
  ).toBe('public');

  expect(
    evaluate(function () {
      /** @access protected */
    })[0].access
  ).toBe('protected');

  expect(
    evaluate(function () {
      /** @access private */
    })[0].access
  ).toBe('private');
});

test('parse - @alias', function () {});

test('parse - @arg', function () {});

test('parse - @argument', function () {});

test('parse - @async', function () {
  expect(
    evaluate(function () {
      /** @async */
    })[0].async
  ).toBe(true);
});

test('parse - @augments', function () {
  expect(
    evaluate(function () {
      /** @augments Foo */
    })[0].augments[0].name
  ).toBe('Foo');
});

test('parse - @description', function () {
  expect(
    evaluate(function () {
      /**
       * This is a free-form description
       * @description This tagged description wins, and [is markdown](http://markdown.com).
       */
    })[0].description
  ).toEqual(
    remarkParse(
      'This tagged description wins, and [is markdown](http://markdown.com).'
    )
  );
});

/*
 * Dossier-style augments tag
 * https://github.com/google/closure-library/issues/746
 */
test('parse - @augments in dossier style', function () {
  expect(
    evaluate(function () {
      /** @augments {Foo} */
    })[0].augments[0].name
  ).toBe('Foo');
});

test('parse - @augments of complex passes through', function () {
  expect(
    evaluate(function () {
      /** @augments {function()} */
    })[0].augments
  ).toEqual([]);
});

test('parse - @author', function () {});

test('parse - @borrows', function () {});

test('parse - @callback', function () {
  expect(
    pick(
      evaluate(function () {
        /** @callback name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    name: 'name',
    kind: 'typedef',
    type: {
      type: 'NameExpression',
      name: 'Function'
    }
  });
});

test('parse - @class', function () {
  expect(
    pick(
      evaluate(function () {
        /** @class */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'class'
  });

  expect(
    pick(
      evaluate(function () {
        /** @class name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    name: 'name',
    kind: 'class'
  });

  expect(
    pick(
      evaluate(function () {
        /** @class {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    name: 'name',
    kind: 'class',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @classdesc', function () {
  expect(
    evaluate(function () {
      /** @classdesc test */
    })[0].classdesc
  ).toEqual(remarkParse('test'));
});

test('parse - @const', function () {});

test('parse - @constant', function () {
  expect(
    pick(
      evaluate(function () {
        /** @constant */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'constant'
  });

  expect(
    pick(
      evaluate(function () {
        /** @constant name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'constant',
    name: 'name'
  });

  expect(
    pick(
      evaluate(function () {
        /** @constant {Object} */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'constant',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });

  expect(
    pick(
      evaluate(function () {
        /** @constant {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'constant',
    name: 'name',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @constructor', function () {});

test('parse - @constructs', function () {});

test('parse - @copyright', function () {
  expect(
    evaluate(function () {
      /** @copyright test */
    })[0].copyright
  ).toEqual(remarkParse('test'));
});

test('parse - @default', function () {});

test('parse - @defaultvalue', function () {});

test('parse - @deprecated', function () {
  expect(
    evaluate(function () {
      /** @deprecated test */
    })[0].deprecated
  ).toEqual(remarkParse('test'));
});

test('parse - @desc', function () {
  expect(
    evaluate(function () {
      /** @desc test */
    })[0].description
  ).toEqual(remarkParse('test'));
});

test('parse - @description', function () {
  expect(
    evaluate(function () {
      /** @description test */
    })[0].description
  ).toEqual(remarkParse('test'));
});

test('parse - description', function () {
  expect(
    evaluate(function () {
      /** test */
    })[0].description
  ).toEqual(remarkParse('test'));
});

test('parse - @emits', function () {});

test('parse - @enum', function () {
  expect(
    pick(
      evaluate(function () {
        /** @enum {string} */
      })[0],
      ['kind', 'type']
    )
  ).toEqual({
    kind: 'enum',
    type: {
      type: 'NameExpression',
      name: 'string'
    }
  });
});

test('parse - @event', function () {
  expect(
    pick(
      evaluate(function () {
        /** @event name */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'event',
    name: 'name'
  });
});

test('parse - @example', function () {
  expect(
    evaluate(function () {
      /** @example test */
    })[0].examples[0]
  ).toEqual({
    description: 'test'
  });

  expect(
    evaluate(function () {
      /**
       * @example
       * a
       * b
       */
    })[0].examples[0]
  ).toEqual({
    description: 'a\nb'
  });

  expect(
    evaluate(function () {
      /**
       * @example <caption>caption</caption>
       * a
       * b
       */
    })[0].examples[0]
  ).toEqual({
    description: 'a\nb',
    caption: remarkParse('caption')
  });

  expect(
    evaluate(function () {
      /** @example */
    })[0].errors[0]
  ).toEqual({
    message: '@example without code',
    commentLineNumber: 0
  });
});

test('parse - @exception', function () {});

test('parse - @exports', function () {});

test('parse - @extends', function () {
  expect(
    evaluate(function () {
      /** @extends Foo */
    })[0].augments[0].name
  ).toEqual('Foo');
});

test('parse - @external', function () {
  expect(
    pick(
      evaluate(function () {
        /** @external name */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'external',
    name: 'name'
  });
});

test('parse - @file', function () {
  expect(
    pick(
      evaluate(function () {
        /** @file */
      })[0],
      ['kind']
    )
  ).toEqual({
    kind: 'file'
  });

  expect(
    pick(
      evaluate(function () {
        /** @file desc */
      })[0],
      ['kind', 'description']
    )
  ).toEqual({
    kind: 'file',
    description: remarkParse('desc')
  });
});

test('parse - @fileoverview', function () {});

test('parse - @fires', function () {});

test('parse - @func', function () {});

test('parse - @function', function () {
  expect(
    pick(
      evaluate(function () {
        /** @function */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'function'
  });

  expect(
    pick(
      evaluate(function () {
        /** @function name */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'function',
    name: 'name'
  });

  // When @function takes a name, it is acting as a shorthand for @name and
  // should detach from code the same way @name does.
  expect(
    evaluate(function () {
      /** @function */
      function foo() {}
    })[0].context.ast
  ).toBeDefined();

  expect(
    evaluate(function () {
      /** @function name */
      function foo() {}
    })[0].context.ast
  ).toBeUndefined();
});

test('parse - @generator', function () {
  expect(
    evaluate(function () {
      /** @generator */
    })[0].generator
  ).toBe(true);
});

test('parse - @global', function () {
  expect(
    evaluate(function () {
      /** @global */
    })[0].scope
  ).toBe('global');
});

test('parse - @hideconstructor', function () {
  expect(
    evaluate(function () {
      /** @hideconstructor */
    })[0].hideconstructor
  ).toBe(true);
});

test('parse - @host', function () {});

test('parse - @ignore', function () {
  expect(
    evaluate(function () {
      /** @ignore */
    })[0].ignore
  ).toBe(true);
});

test('parse - @implements', function () {
  expect(
    evaluate(function () {
      /** @implements {Foo} */
    })[0].implements[0].name
  ).toEqual('Foo');
});

test('parse - @inheritdoc', function () {});

test('parse - @inner', function () {
  expect(
    evaluate(function () {
      /** @inner*/
    })[0].scope
  ).toBe('inner');
});

test('parse - @instance', function () {
  expect(
    evaluate(function () {
      /** @instance*/
    })[0].scope
  ).toBe('instance');
});

test('parse - @interface', function () {
  expect(
    evaluate(function () {
      /** @interface */
    })[0].kind
  ).toEqual('interface');

  expect(
    evaluate(function () {
      /** @interface Foo */
    })[0].name
  ).toEqual('Foo');
});

test('parse - @kind', function () {
  expect(
    evaluate(function () {
      /** @kind class */
    })[0].kind
  ).toBe('class');
});

test('parse - @license', function () {});

test('parse - @listens', function () {});

test('parse - @member', function () {
  expect(
    pick(
      evaluate(function () {
        /** @member */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'member'
  });

  expect(
    pick(
      evaluate(function () {
        /** @member name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'member',
    name: 'name'
  });

  expect(
    pick(
      evaluate(function () {
        /** @member {Object} */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'member',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });

  expect(
    pick(
      evaluate(function () {
        /** @member {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'member',
    name: 'name',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @memberof', function () {
  expect(
    evaluate(function () {
      /** @memberof test */
    })[0].memberof
  ).toBe('test');
});

test('parse - @method', function () {});

test('parse - @mixes', function () {});

test('parse - @mixin', function () {
  expect(
    pick(
      evaluate(function () {
        /** @mixin */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'mixin'
  });

  expect(
    pick(
      evaluate(function () {
        /** @mixin name */
      })[0],
      ['kind', 'name']
    )
  ).toEqual({
    kind: 'mixin',
    name: 'name'
  });
});

test('parse - @module', function () {
  expect(
    pick(
      evaluate(function () {
        /** @module */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'module'
  });

  expect(
    pick(
      evaluate(function () {
        /** @module name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'module',
    name: 'name'
  });

  expect(
    pick(
      evaluate(function () {
        /** @module {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'module',
    name: 'name',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @name', function () {
  expect(
    evaluate(function () {
      /** @name test */
    })[0].name
  ).toBe('test');

  expect(
    evaluate(function () {
      /** @name foo */
      const bar = 0;
    })[0].context.ast
  ).toBeUndefined();
});

test('parse - @namespace', function () {
  expect(
    pick(
      evaluate(function () {
        /** @namespace */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'namespace'
  });

  expect(
    pick(
      evaluate(function () {
        /** @namespace name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'namespace',
    name: 'name'
  });

  expect(
    pick(
      evaluate(function () {
        /** @namespace {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'namespace',
    name: 'name',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @override', function () {
  expect(
    evaluate(function () {
      /** @override */
    })[0].override
  ).toBe(true);
});

test('parse - @overview', function () {});

test('parse - @param', function () {
  expect(
    evaluate(function () {
      /** @param test */
    })[0].params[0]
  ).toEqual({
    name: 'test',
    title: 'param',
    lineNumber: 0
  });

  expect(
    evaluate(function () {
      /** @param {number} test */
    })[0].params[0]
  ).toEqual({
    name: 'test',
    title: 'param',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    lineNumber: 0
  });

  expect(
    evaluate(function () {
      /** @param {number} test - desc */
    })[0].params[0]
  ).toEqual({
    name: 'test',
    title: 'param',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    description: remarkParse('desc'),
    lineNumber: 0
  });
});

test('parse - @private', function () {
  expect(
    evaluate(function () {
      /** @private */
    })[0].access
  ).toBe('private');
});

test('parse - @prop', function () {
  expect(
    evaluate(function () {
      /** @prop {number} test */
    })[0].properties[0]
  ).toEqual({
    name: 'test',
    title: 'property',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    lineNumber: 0
  });

  expect(
    evaluate(function () {
      /** @prop {number} test - desc */
    })[0].properties[0]
  ).toEqual({
    name: 'test',
    title: 'property',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    description: remarkParse('desc'),
    lineNumber: 0
  });
});

test('parse - @property', function () {
  expect(
    evaluate(function () {
      /** @property {number} test */
    })[0].properties[0]
  ).toEqual({
    name: 'test',
    title: 'property',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    lineNumber: 0
  });

  expect(
    evaluate(function () {
      /** @property {number} test - desc */
    })[0].properties[0]
  ).toEqual({
    name: 'test',
    title: 'property',
    type: {
      name: 'number',
      type: 'NameExpression'
    },
    description: remarkParse('desc'),
    lineNumber: 0
  });
});

test('parse - @protected', function () {
  expect(
    evaluate(function () {
      /** @protected */
    })[0].access
  ).toBe('protected');
});

test('parse - @public', function () {});

test('parse - @readonly', function () {
  expect(
    evaluate(function () {
      /** @readonly */
    })[0].readonly
  ).toBe(true);
});

test('parse - @requires', function () {});

test('parse - @return', function () {
  expect(
    evaluate(function () {
      /** @return test */
    })[0].returns[0]
  ).toEqual({
    title: 'returns',
    description: remarkParse('test')
  });

  expect(
    evaluate(function () {
      /** @return {number} test */
    })[0].returns[0]
  ).toEqual({
    description: remarkParse('test'),
    title: 'returns',
    type: {
      name: 'number',
      type: 'NameExpression'
    }
  });
});

test('parse - @returns', function () {
  expect(
    evaluate(function () {
      /** @returns test */
    })[0].returns[0]
  ).toEqual({
    title: 'returns',
    description: remarkParse('test')
  });

  expect(
    evaluate(function () {
      /** @returns {number} test */
    })[0].returns[0]
  ).toEqual({
    description: remarkParse('test'),
    title: 'returns',
    type: {
      name: 'number',
      type: 'NameExpression'
    }
  });
});

test('parse - @see', function () {
  expect(
    evaluate(function () {
      /** @see [test](#test) */
    })[0].sees
  ).toEqual([
    {
      title: 'sees',
      description: remarkParse('[test](#test)')
    }
  ]);

  expect(
    evaluate(function () {
      /**
       * @see [a](#a)
       * @see [b](#b)
       */
    })[0].sees
  ).toEqual([
    {
      title: 'sees',
      description: remarkParse('[a](#a)')
    },
    {
      title: 'sees',
      description: remarkParse('[b](#b)')
    }
  ]);
});

test('parse - @since', function () {});

test('parse - @static', function () {
  expect(
    evaluate(function () {
      /** @static */
    })[0].scope
  ).toBe('static');
});

test('parse - @summary', function () {
  expect(
    evaluate(function () {
      /** @summary test */
    })[0].summary
  ).toEqual(remarkParse('test'));
});

test('parse - @this', function () {});

test('parse - @throws', function () {
  expect(
    evaluate(function () {
      /** @throws desc */
    })[0].throws[0]
  ).toEqual({
    description: remarkParse('desc')
  });

  expect(
    evaluate(function () {
      /** @throws {Error} */
    })[0].throws[0]
  ).toEqual({
    type: {
      name: 'Error',
      type: 'NameExpression'
    }
  });

  expect(
    evaluate(function () {
      /** @throws {Error} desc */
    })[0].throws[0]
  ).toEqual({
    type: {
      name: 'Error',
      type: 'NameExpression'
    },
    description: remarkParse('desc')
  });

  expect(
    evaluate(function () {
      /**
       * @throws a
       * @throws b
       */
    })[0].throws
  ).toEqual([
    {
      description: remarkParse('a')
    },
    {
      description: remarkParse('b')
    }
  ]);
});

test('parse - @todo', function () {
  expect(
    evaluate(function () {
      /** @todo test */
    })[0].todos
  ).toEqual([remarkParse('test')]);

  expect(
    evaluate(function () {
      /**
       * @todo a
       * @todo b
       */
    })[0].todos
  ).toEqual([remarkParse('a'), remarkParse('b')]);
});

test('parse - @tutorial', function () {});

test('parse - @type', function () {});

test('parse - @typedef', function () {
  expect(
    pick(
      evaluate(function () {
        /** @typedef {Object} name */
      })[0],
      ['kind', 'name', 'type']
    )
  ).toEqual({
    kind: 'typedef',
    name: 'name',
    type: {
      type: 'NameExpression',
      name: 'Object'
    }
  });
});

test('parse - @var', function () {});

test('parse - @variation', function () {
  expect(
    evaluate(function () {
      /** @variation 1 */
    })[0].variation
  ).toBe(1);
});

test('parse - @version', function () {});

test('parse - @virtual', function () {});

test('parse - @yield', function () {
  expect(
    evaluate(function () {
      /** @yield test */
    })[0].yields[0]
  ).toEqual({
    title: 'yields',
    description: remarkParse('test')
  });

  expect(
    evaluate(function () {
      /** @yield {number} test */
    })[0].yields[0]
  ).toEqual({
    description: remarkParse('test'),
    title: 'yields',
    type: {
      name: 'number',
      type: 'NameExpression'
    }
  });
});

test('parse - @yields', function () {
  expect(
    evaluate(function () {
      /** @yields test */
    })[0].yields[0]
  ).toEqual({
    title: 'yields',
    description: remarkParse('test')
  });

  expect(
    evaluate(function () {
      /** @yields {number} test */
    })[0].yields[0]
  ).toEqual({
    description: remarkParse('test'),
    title: 'yields',
    type: {
      name: 'number',
      type: 'NameExpression'
    }
  });
});

test('parse - unknown tag', function () {
  expect(
    evaluate(function () {
      /** @unknown */
    })[0].errors[0]
  ).toEqual({
    message: 'unknown tag @unknown',
    commentLineNumber: 0
  });
});

test('parse - {@link}', function () {
  expect(
    evaluate(function () {
      /** {@link Foo} */
    })[0].description
  ).toEqual(addJSDocTag(remarkParse('[Foo](Foo)')));

  expect(
    evaluate(function () {
      /** {@link Foo|text} */
    })[0].description
  ).toEqual(addJSDocTag(remarkParse('[text](Foo)')));

  expect(
    evaluate(function () {
      /** {@link Foo text} */
    })[0].description
  ).toEqual(addJSDocTag(remarkParse('[text](Foo)')));
});

test('parse - {@linkcode}', function () {});

test('parse - {@linkplain}', function () {});

test('parse - {@tutorial}', function () {
  expect(
    evaluate(function () {
      /** {@tutorial id} */
    })[0].description
  ).toEqual({
    type: 'root',
    children: [
      {
        type: 'paragraph',
        children: [
          {
            type: 'tutorial',
            url: 'id',
            jsdoc: true,
            title: null,
            children: [
              {
                type: 'text',
                value: 'id'
              }
            ]
          }
        ]
      }
    ]
  });

  expect(
    evaluate(function () {
      /** {@tutorial id|text} */
    })[0].description
  ).toEqual({
    type: 'root',
    children: [
      {
        type: 'paragraph',
        children: [
          {
            type: 'tutorial',
            url: 'id',
            jsdoc: true,
            title: null,
            children: [
              {
                type: 'text',
                value: 'text'
              }
            ]
          }
        ]
      }
    ]
  });

  expect(
    evaluate(function () {
      /** {@tutorial id text} */
    })[0].description
  ).toEqual({
    type: 'root',
    children: [
      {
        type: 'paragraph',
        children: [
          {
            type: 'tutorial',
            url: 'id',
            jsdoc: true,
            title: null,
            children: [
              {
                type: 'text',
                value: 'text'
              }
            ]
          }
        ]
      }
    ]
  });
});
