/* eslint-disable import/no-commonjs */

/**
 * @fileoverview Generates JSON output based on exportable symbols.
 */
const assert = require('assert');
const path = require('path');

/**
 * Publish hook for the JSDoc template.  Writes to JSON stdout.
 * @param {Function} data The root of the Taffy DB containing doclet records.
 * @param {Object} opts Options.
 * @return {Promise} A promise that resolves when writing is complete.
 */
exports.publish = function (data, opts) {
  function getTypes(data) {
    const types = [];
    data.forEach(function (name) {
      types.push(name.replace(/^function$/, 'Function'));
    });
    return types;
  }

  // get all doclets that have exports
  const classes = {};
  const docs = data(
    [
      {define: {isObject: true}},
      function () {
        if (this.kind == 'class') {
          classes[this.longname] = this;
          return true;
        }
        return (
          this.meta &&
          this.meta.path &&
          this.longname.indexOf('<anonymous>') !== 0 &&
          this.longname !== 'module:ol'
        );
      },
    ],
    {kind: {'!is': 'file'}},
    {kind: {'!is': 'event'}},
    {kind: {'!is': 'module'}}
  ).get();

  // get symbols data, filter out those that are members of private classes
  const symbols = [];
  const defines = [];
  const typedefs = [];
  const externs = [];
  let base = [];
  const augments = {};
  const symbolsByName = {};
  docs
    .filter(function (doc) {
      let include = true;
      const constructor = doc.memberof;
      if (
        constructor &&
        constructor.substr(-1) === '_' &&
        constructor.indexOf('module:') === -1
      ) {
        assert.strictEqual(
          doc.inherited,
          true,
          'Unexpected export on private class: ' + doc.longname
        );
        include = false;
      }
      return include;
    })
    .forEach(function (doc) {
      const isExterns = /[\\\/]externs$/.test(doc.meta.path);
      if (doc.define) {
        defines.push({
          name: doc.longname,
          description: doc.description,
          path: path.join(doc.meta.path, doc.meta.filename),
          default: doc.define.default,
        });
      } else if (doc.type && (doc.kind == 'typedef' || doc.isEnum === true)) {
        typedefs.push({
          name: doc.longname,
          types: getTypes(doc.type.names),
        });
      } else {
        const symbol = {
          name: doc.longname,
          kind: doc.kind,
          description: doc.classdesc || doc.description,
          path: path.join(doc.meta.path, doc.meta.filename),
        };
        if (doc.augments) {
          symbol.extends = doc.augments[0];
        }
        if (doc.virtual) {
          symbol.virtual = true;
        }
        if (doc.type) {
          symbol.types = getTypes(doc.type.names);
        }
        if (doc.params) {
          const params = [];
          doc.params.forEach(function (param) {
            const paramInfo = {
              name: param.name,
            };
            params.push(paramInfo);
            paramInfo.types = getTypes(param.type.names);
            if (typeof param.variable == 'boolean') {
              paramInfo.variable = param.variable;
            }
            if (typeof param.optional == 'boolean') {
              paramInfo.optional = param.optional;
            }
            if (typeof param.nullable == 'boolean') {
              paramInfo.nullable = param.nullable;
            }
          });
          symbol.params = params;
        }
        if (doc.returns) {
          symbol.returns = {
            types: getTypes(doc.returns[0].type.names),
          };
          if (typeof doc.returns[0].nullable == 'boolean') {
            symbol.returns.nullable = doc.returns[0].nullable;
          }
        }
        if (doc.tags) {
          doc.tags.every(function (tag) {
            if (tag.title == 'template') {
              symbol.template = tag.value;
              return false;
            }
            return true;
          });
        }
        if (doc.isDefaultExport) {
          symbol.isDefaultExport = true;
        }

        const target = isExterns ? externs : symbols;
        const existingSymbol = symbolsByName[symbol.name];
        if (existingSymbol) {
          const idx = target.indexOf(existingSymbol);
          target.splice(idx, 1);
        }
        target.push(symbol);
        symbolsByName[symbol.name] = symbol;

        if (symbol.extends) {
          while (
            symbol.extends in classes &&
            classes[symbol.extends].augments
          ) {
            symbol.extends = classes[symbol.extends].augments[0];
          }
          if (symbol.extends) {
            augments[symbol.extends] = true;
          }
        }
      }
    });

  base = base.filter(function (symbol) {
    return symbol.name in augments || symbol.virtual;
  });

  return new Promise(function (resolve, reject) {
    process.stdout.write(
      JSON.stringify(
        {
          symbols: symbols,
          defines: defines,
          typedefs: typedefs,
          externs: externs,
          base: base,
        },
        null,
        2
      )
    );
  });
};
