const jsAstParser = require('./lib/js-ast-parser');
const templateAstParser = require('./lib/template-ast-parser');
const jsonAstParser = require('./lib/json-ast-parser');
const commonEvents = require('../../config/common-events.json');

class TemplateChecker {

  /**
     * constructor
     * @param {Object} trees
     * {
     *    templateAst: {},
     *    scriptAst: {},
     *    jsonAst: {}
     * }
     */
  constructor(filePath = '', lintedResult = {}) {
    this._filePath = filePath;
    this._platform = lintedResult.template.platform;

    this._templateAst = lintedResult.template.ast;
    this._scriptAst = lintedResult.script.ast;
    this._jsonAst = lintedResult.json.obj;

    this._parsedTemplateResults = '';
    this._parsedScriptResults = '';
    this._usingComponents = '';

    this.parseAllParts();
  }

  parseAllParts() {
    this._usingComponents = jsonAstParser.getUsingComponents(this._jsonAst, this._filePath);
    this._parsedScriptResults = jsAstParser.getParseResults(this._scriptAst);
    this._parsedTemplateResults = templateAstParser.getParseResults(this._templateAst, {
      usingComponents: Object.keys(this._usingComponents),
      platform: this._platform
    });
  }

  checkCustomizedComponents() {
    let issues = [];
    let usingComponents = this._usingComponents;
    let customizedComponets = this._parsedTemplateResults.customizedComponents;

    customizedComponets.forEach((component) => {
      Object.entries(component).forEach((compInfo) => {
        let compName = compInfo[0];
        if (usingComponents[compName] && usingComponents[compName].isCml) {
          let { props, events } = component[compName];
          let { props: usingProps, events: usingEvents } = usingComponents[compName];
          usingProps = usingProps
            .map((prop) => prop.name)
            .join('|');
          usingEvents = usingEvents
            .map((event) => event.name)
            .concat(commonEvents.events)
            .join('|');
          usingProps = `|${usingProps}|`;
          usingEvents = `|${usingEvents}|`;
          props.filter((prop) => usingProps.indexOf('|' + prop.name + '|') === -1).forEach((prop) => {
            issues.push({
              line: prop.pos[0],
              column: prop.pos[1],
              token: prop.rawName,
              msg: `The property "${prop.rawName}" is not a property of component "${compName}" which path is: ${usingComponents[compName].path}`
            });
          });

          events.filter((event) => usingEvents.indexOf('|' + event.name + '|') === -1).forEach((event) => {
            issues.push({
              line: event.pos[0],
              column: event.pos[1],
              token: event.name,
              msg: `The event "${event.name}" is not defined in component "${compName}" which path is: ${usingComponents[compName].path}`
            });
          });
        }
      });
    });

    return issues;
  }

  checkTemplateAndScript() {
    let jsAstResults = this._parsedScriptResults;
    let templateAstResults = this._parsedTemplateResults;
    let issues = [];

    templateAstResults.methods.forEach(method => {
      if (jsAstResults.methods.indexOf(method.name) === -1) {
        issues.push({
          line: method.pos[0],
          column: method.pos[1],
          token: method.name,
          msg: `method: "${method.name}" is not defined.`
        });
      }
    });

    templateAstResults.vars.forEach((varItem) => {
      if (jsAstResults.vars.indexOf(varItem.name) === -1) {
        issues.push({
          line: varItem.pos[0],
          column: varItem.pos[1],
          token: varItem.name,
          msg: `variable: "${varItem.name}" is not defined.`
        });
      }
    });

    return issues;
  }

  check() {
    return [...this.checkCustomizedComponents(), ...this.checkTemplateAndScript()];
  }
}

module.exports = TemplateChecker;
