/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const ts = require('typescript');
const { ApiDigestInfo } = require('./api_data');
const { DiffReporter } = require('./reporter');
const { StatusCode } = require('./reporter');
const { getCheckApiVersion } = require('../../api_check_plugin/src/utils')

class TagItem {
  constructor() { }

  addSinceVersion(version) {
    this.setProperty('sinceVersion', version);
  }

  getSinceVersion() {
    return this.sinceVersion;
  }

  addSyscap(sysCap) {
    this.setProperty('sysCap', sysCap);
  }

  getSyscap() {
    return this.sysCap;
  }

  addAppModel(model) {
    this.setProperty('appModel', model);
  }

  getAppModel() {
    return this.appModel;
  }

  addDeprecated(deprecated) {
    this.setProperty('deprecated', deprecated);
  }

  getDeprecated() {
    return this.deprecated;
  }

  addErrorCode(errorCode) {
    this.setProperty('errorCode', errorCode);
  }

  getErrorCode() {
    return this.errorCode;
  }

  addApiLevel(level) {
    this.setProperty('apiLevel', level);
  }

  getApiLevel() {
    return this.apiLevel;
  }

  addTypeTag(type) {
    this.setProperty('type', type);
  }

  getTypeTag() {
    return this.type;
  }

  addUseInstead(useinstead) {
    this.setProperty('useinstead', useinstead);
  }

  getUseInstead() {
    return this.useinstead;
  }

  addPermission(permission) {
    this.setProperty('permission', permission);
  }

  getPermission() {
    return this.permission;
  }

  addForm(form) {
    this.setProperty('form', form);
  }

  getForm() {
    return this.form;
  }

  addCrossplatform(crossplatform) {
    this.setProperty('crossplatform', crossplatform);
  }

  getCrossplatform() {
    return this.crossplatform;
  }

  setProperty(name, value) {
    if (this[name]) {
      this[name].push(value);
    } else {
      this[name] = [value];
    }
  }
}

function isArrayEquals(first, second) {
  if (!first && !second) {
    return true;
  }
  const ret = first && second && first.length === second.length;
  if (ret) {
    first.sort();
    second.sort();
    for (let index = 0; index < first.length; index++) {
      if (first[index] !== second[index]) {
        return false;
      }
    }
  }
  return ret;
}

function arrayToString(array) {
  if (!array || array.length === 0) {
    return '';
  }
  return array.join();
}

/**
 * 获取API @deprecated 标签信息, 继承父类
 * 
 * @param {ApiDigestInfo} api 
 * @returns {Array}
 */
function getApiDeprecated(api) {
  let curApi = api;
  while (curApi) {
    const jsdocTagItem = getTagItemFromJSDoc(curApi);
    if (jsdocTagItem.getDeprecated()) {
      return jsdocTagItem.getDeprecated();
    }
    curApi = curApi.getParent();
  }
  return [];
}

function matchSyscapInFile(api) {
  let syscap = getApiSyscap(api)[0];
  let curApi = api;
  while (!syscap && !ts.isSourceFile(curApi.node)) {
    curApi = curApi.getParent();
  }
  if (!syscap && ts.isSourceFile(curApi.node)) {
    const fileContent = curApi.node.getFullText();
    if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(fileContent)) {
      fileContent.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => {
        syscap = sysCapInfo.replace(/\@syscap/g, '').trim();
      });
    }
  }
  return syscap;
}

/**
 * 获取 API @syscap 标签信息,继承父类
 * 
 * @param {ApiDigestInfo} api 
 * @returns {Array}
 */
function getApiSyscap(api) {
  let curApi = api;
  while (curApi && !ts.isSourceFile(curApi.node)) {
    const jsdocTagItem = getTagItemFromJSDoc(curApi);
    if (jsdocTagItem.getSyscap()) {
      return jsdocTagItem.getSyscap();
    }
    curApi = curApi.getParent();
  }
  return [];
}

/**
 * 获取API @useinstead,继承父类
 * 
 * @param {ApiDigestInfo} api 
 * @returns {Array}
 */
function getApiUseInstead(api) {
  let curApi = api;
  while (curApi) {
    const jsdocTagItem = getTagItemFromJSDoc(curApi);
    if (jsdocTagItem.getUseInstead()) {
      return jsdocTagItem.getUseInstead();
    }
    curApi = curApi.getParent();
  }
  return [];
}

/**
 * 从 JSDoc 对象获取所有Tag标签信息
 * 
 * @param {ApiDigestInfo} api 
 * @returns {Object}
 */
function getTagItemFromJSDoc(api) {
  let jsdocTagItem = api.getJSDocTagItem();
  if (!jsdocTagItem) {
    jsdocTagItem = createTagItemFromJSDoc(api.jsdoc);
    api.setJSDocTagItem(jsdocTagItem);
  }
  return jsdocTagItem;
}

function wrapApiChanges(api, statusCode, oldMessage, newMessage, hint, oldNode, newNode, syscap) {
  return {
    api: api,
    statusCode: statusCode,
    oldMessage: oldMessage,
    newMessage: newMessage,
    hint: hint,
    oldNode: oldNode,
    newNode: newNode,
    syscap: syscap,
  };
}

/**
 * 比较JSDoc的差异
 * 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {DiffReporter} diffReporter 
 */
function compareJSDocs(oldApi, newApi, diffReporter) {
  const oldTagItem = getTagItemFromJSDoc(oldApi);
  const newTagItem = getTagItemFromJSDoc(newApi);
  const useinstead = getApiUseInstead(newApi);
  const hint = useinstead.length > 0 ? `useinstead: ${useinstead[0]}` : '';
  diffHistoricalJsDoc(oldApi, newApi, diffReporter, hint);
  diffErrorCode(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffPermission(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffForm(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffCrossplatform(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffDeprecated(diffReporter, oldApi, newApi, hint);
  diffApiLevel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffAppModel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
  diffType(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint);
}

function getLatestVersion(jsdocs) {
  if (!jsdocs || jsdocs.length === 0) {
    return;
  }
  const jsdoc = jsdocs[jsdocs.length - 1];
  let apiLatestVersion = '';
  jsdoc.tags?.forEach(tagObject => {
    if (tagObject.tag === 'since') {
      apiLatestVersion = tagObject.name;
    }
  })
  return apiLatestVersion;
}

function diffHistoricalJsDoc(oldApi, newApi, diffReporter, hint) {
  const currentVersion = getCheckApiVersion();
  const oldJsDocTextArr = oldApi.getAstNode().getFullText().replace(oldApi.getAstNode().getText(), '').split('*/');
  const newJsDocTextArr = newApi.getAstNode().getFullText().replace(oldApi.getAstNode().getText(), '').split('*/');
  if (!oldApi.jsdoc) {
    return;
  }
  const oldLatestVersion = getLatestVersion(oldApi.jsdoc);
  const newLatestVersion = getLatestVersion(newApi.jsdoc);
  if (oldLatestVersion === currentVersion) {
    oldJsDocTextArr.splice(-2);
  } else {
    oldJsDocTextArr.splice(-1);
  }

  if (newLatestVersion === currentVersion) {
    newJsDocTextArr.splice(-2);
  } else {
    newJsDocTextArr.splice(-1);
  }

  if (oldJsDocTextArr.length !== newJsDocTextArr.length) {
    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.HOSTORICAL_JSDOC_CHANGE,
      '',
      '',
      hint,
      oldApi.node,
      newApi.node
    ));
    return;
  }
  for (let i = 0; i < oldJsDocTextArr.length; i++) {
    if (oldJsDocTextArr[i].replace(/\r|\n|\s+/g, '') !== newJsDocTextArr[i].replace(/\r|\n|\s+/g, '')) {
      diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.HOSTORICAL_JSDOC_CHANGE,
        '',
        '',
        hint,
        oldApi.node,
        newApi.node
      ));
    }
  }
}

/**
 * 比较@type
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffType(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  const oldType = oldTagItem.getTypeTag();
  const newType = newTagItem.getTypeTag();
  if (!isArrayEquals(oldType, newType)) {
    diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.TYPE_CHNAGES,
      arrayToString(oldType), arrayToString(newType),
      hint, '', '', matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.TYPE_CHNAGES,
      arrayToString(oldType),
      arrayToString(newType),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@FAModelOnly @StageModelOnly
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffAppModel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  const oldAppModel = oldTagItem.getAppModel();
  const newAppModel = newTagItem.getAppModel();
  if (!isArrayEquals(oldAppModel, newTagItem.getAppModel())) {
    diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.MODEL_CHNAGES,
      arrayToString(oldAppModel), arrayToString(newAppModel),
      hint, '', '', matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.MODEL_CHNAGES,
      arrayToString(oldAppModel),
      arrayToString(newAppModel),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@systemapi
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffApiLevel(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  const oldApiLevel = oldTagItem.getApiLevel();
  const newApiLevel = newTagItem.getApiLevel();
  if (!isArrayEquals(oldApiLevel, newApiLevel)) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.SYSTEM_API_CHNAGES,
      arrayToString(oldApiLevel),
      arrayToString(newApiLevel),
      hint, '', '', matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.SYSTEM_API_CHNAGES,
      arrayToString(oldApiLevel),
      arrayToString(newApiLevel),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@crossplatform
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffCrossplatform(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  if (!isArrayEquals(oldTagItem.getCrossplatform(), newTagItem.getCrossplatform())) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.CROSSPLATFORM_CHANGED, arrayToString(oldTagItem.getCrossplatform()),
      arrayToString(newTagItem.getCrossplatform()),
      hint, '', '', matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.CROSSPLATFORM_CHANGED,
      arrayToString(oldTagItem.getCrossplatform()),
      arrayToString(newTagItem.getCrossplatform()),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@form
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffForm(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  if (!isArrayEquals(oldTagItem.getForm(), newTagItem.getForm())) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.FORM_CHANGED, arrayToString(oldTagItem.getForm()),
      arrayToString(newTagItem.getForm()),
      hint, '', '', matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.FORM_CHANGED,
      arrayToString(oldTagItem.getForm()),
      arrayToString(newTagItem.getForm()),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@syscap
 * 
 * @param {DiffReporter} diffReporter 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffSyscap(diffReporter, oldApi, newApi, hint) {
  const oldSyscap = getApiSyscap(oldApi);
  const newSyscap = getApiSyscap(newApi);
  if (!isArrayEquals(oldSyscap, newSyscap)) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.SYSCAP_CHANGES, arrayToString(oldSyscap),
      arrayToString(newSyscap),
      hint, '', '', matchSyscapInFile(newApi)
    ));
    diffReporter.addDiffInfo(wrapApiChanges(
      newApi, StatusCode.SYSCAP_CHANGES, arrayToString(oldSyscap),
      arrayToString(newSyscap),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@since
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffSinceVersion(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  const oldVersion = oldTagItem.getSinceVersion();
  const newVersion = newTagItem.getSinceVersion();
  if (!isArrayEquals(oldVersion, newVersion)) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.VERSION_CHNAGES, arrayToString(oldVersion),
      arrayToString(newVersion),
      hint, '', '', matchSyscapInFile(newApi)
    ));
    diffReporter.addDiffInfo(wrapApiChanges(
      newApi, StatusCode.VERSION_CHNAGES, arrayToString(oldVersion),
      arrayToString(newVersion),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较@deprecated
 * 
 * @param {DiffReporter} diffReporter 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffDeprecated(diffReporter, oldApi, newApi, hint) {
  const oldDeprecated = getApiDeprecated(oldApi);
  const newDeprecated = getApiDeprecated(newApi);
  if (!isArrayEquals(oldDeprecated, newDeprecated)) {
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, StatusCode.DEPRECATED_CHNAGES,
      arrayToString(oldDeprecated), arrayToString(newDeprecated),
      hint, '', '', matchSyscapInFile(newApi)
    ));
    diffReporter.addDiffInfo(wrapApiChanges(
      newApi, StatusCode.DEPRECATED_CHNAGES, arrayToString(oldDeprecated),
      arrayToString(newDeprecated),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较权限的差异@permission
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffPermission(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  if (!isArrayEquals(oldTagItem.getPermission(), newTagItem.getPermission())) {
    diffReporter.addChangedApi(wrapApiChanges(newApi, StatusCode.PERMISSION_CHANGES,
      arrayToString(oldTagItem.getPermission()),
      arrayToString(newTagItem.getPermission()),
      hint,
      oldApi.node,
      newApi.node,
      matchSyscapInFile(newApi)
    ));

    diffReporter.addDiffInfo(wrapApiChanges(newApi, StatusCode.PERMISSION_CHANGES,
      arrayToString(oldTagItem.getPermission()),
      arrayToString(newTagItem.getPermission()),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 比较错误码的差异@throws
 * 
 * @param {DiffReporter} diffReporter 
 * @param {TagItem} oldTagItem 
 * @param {TagItem} newTagItem 
 * @param {ApiDigestInfo} oldApi 
 * @param {ApiDigestInfo} newApi 
 * @param {string} hint 
 */
function diffErrorCode(diffReporter, oldTagItem, newTagItem, oldApi, newApi, hint) {
  if (!isArrayEquals(oldTagItem.getErrorCode(), newTagItem.getErrorCode())) {
    let statusCode = '';
    if (oldTagItem.getErrorCode() === undefined) {
      statusCode = StatusCode.NEW_ERRORCODE;
    } else {
      statusCode = StatusCode.ERRORCODE_CHANGES;
    }
    diffReporter.addChangedApi(wrapApiChanges(
      newApi, statusCode, arrayToString(oldTagItem.getErrorCode()),
      arrayToString(newTagItem.getErrorCode()),
      hint,
      oldApi.node,
      newApi.node,
      matchSyscapInFile(newApi)
    ));
    diffReporter.addDiffInfo(wrapApiChanges(
      newApi, statusCode, arrayToString(oldTagItem.getErrorCode()),
      arrayToString(newTagItem.getErrorCode()),
      hint,
      oldApi.node,
      newApi.node
    ));
  }
}

/**
 * 解析 @since 7
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendSinceTag(tagObject, tagItem) {
  tagItem.addSinceVersion(tagObject.name ? tagObject.name : '');
}

/**
 * 解析 @syscap
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendSyscapTag(tagObject, tagItem) {
  tagItem.addSyscap(tagObject.name ? tagObject.name : '');
}

/**
 * 解析 @permission
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendPermissionTag(tagObject, tagItem) {
  const permissionRegExp = RegExp(/ohos\.permission\.\w+/g);
  let sourceText = '';
  tagObject.source.forEach((src) => {
    sourceText += src.source;
  });
  const permissionArray = sourceText.match(permissionRegExp);
  if (permissionArray) {
    permissionArray.forEach((permission) => {
      tagItem.addPermission(permission);
    });
  }
}

/**
 * 解析 @deprecated 标签
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendDeprecatedTag(tagObject, tagItem) {
  tagItem.addDeprecated(tagObject.description);
}

/**
 * 解析 @FAModelOnly @StageModelOnly
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendModelTag(tagObject, tagItem) {
  tagItem.addAppModel(tagObject.tag);
}

/**
 * 解析 @systemapi
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendApiLevelTag(tagObject, tagItem) {
  tagItem.addApiLevel(tagObject.tag);
}

/**
 * 解析 @type {string}
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendTypeTag(tagObject, tagItem) {
  tagItem.addTypeTag(tagObject.type);
}

/**
 * 解析 @useinstedad 标签
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendUseInsteadTag(tagObject, tagItem) {
  tagItem.addUseInstead(tagObject.name);
}

/**
 * 解析 @throws { BusinessError } 201 - 标签
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendErrorCodeTag(tagObject, tagItem) {
  tagItem.addErrorCode(tagObject.name);
}

/**
 * 解析 @form - 标签
 * 
 * @param {Object} tagObject 
 * @param {TagItem} tagItem 
 */
function appendForm(tagObject, tagItem) {
  tagItem.addForm(tagObject.tag);
}

function appendCrossplatform(tagObject, tagItem) {
  tagItem.addCrossplatform(tagObject.tag);
}

const tagHandlerMap = new Map([
  ['syscap', appendSyscapTag],
  ['permission', appendPermissionTag],
  ['deprecated', appendDeprecatedTag],
  ['famodelonly', appendModelTag],
  ['stagemodelonly', appendModelTag],
  ['systemapi', appendApiLevelTag],
  ['type', appendTypeTag],
  ['useinstead', appendUseInsteadTag],
  ['throws', appendErrorCodeTag],
  ['form', appendForm],
  ['crossplatform', appendCrossplatform]
]);



/**
 * 从 comment-parser 库解析的JSDoc对象中提取标签信息
 * 
 * @param {Object} jsdocs 
 */
function createTagItemFromJSDoc(jsdocs) {
  const tagItem = new TagItem();
  if (!jsdocs || jsdocs.length === 0) {
    return tagItem;
  }
  const singleJSDoc = jsdocs[jsdocs.length - 1];
  const firstJsDoc = jsdocs[0];
  if (singleJSDoc.tags) {
    singleJSDoc.tags.forEach((tagObject) => {
      const handler = tagHandlerMap.get(tagObject.tag.toLowerCase());
      if (handler) {
        handler(tagObject, tagItem);
      }
    });
  }

  if (firstJsDoc.tags) {
    firstJsDoc.tags.forEach((tagObject) => {
      if (tagObject.tag.toLowerCase() === 'since') {
        appendSinceTag(tagObject, tagItem);
      }
    });
  }
  return tagItem;
}

exports.JSDocDiffer = {
  collectJSDocDiffs: compareJSDocs,
};