/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
import { TSESTree } from '@typescript-eslint/utils';
import {
  RuleContext,
  RuleFix,
  RuleFixer,
} from '@typescript-eslint/utils/ts-eslint';

import { getSourceCode } from './typescript';



export function appendObjectProperties(context: RuleContext<any, any>, fixer: RuleFixer, objectNode: TSESTree.ObjectExpression, properties: string[]): RuleFix {
  // todo: may not handle empty objects too well
  const lastProperty = objectNode.properties[objectNode.properties.length - 1];
  const source = getSourceCode(context);
  const nextToken = source.getTokenAfter(lastProperty);

  // todo: newline & indentation are hardcoded for @Component({})
  // todo: we're assuming that we need trailing commas, what if we don't?
  const newPart = '\n' + properties.map(p => `  ${p},`).join('\n');

  if (nextToken !== null && nextToken.value === ',') {
    return fixer.insertTextAfter(nextToken, newPart);
  } else {
    return fixer.insertTextAfter(lastProperty, ',' + newPart);
  }
}

export function appendArrayElement(context: RuleContext<any, any>, fixer: RuleFixer, arrayNode: TSESTree.ArrayExpression, value: string): RuleFix {
  const source = getSourceCode(context);

  if (arrayNode.elements.length === 0) {
    // This is the first element
    const openArray = source.getTokenByRangeStart(arrayNode.range[0]);

    if (openArray == null) {
      throw new Error('Unexpected null token for opening square bracket');
    }

    // safe to assume the list is single-line
    return fixer.insertTextAfter(openArray, `${value}`);
  } else {
    const lastElement = arrayNode.elements[arrayNode.elements.length - 1];

    if (lastElement == null) {
      throw new Error('Unexpected null node in array');
    }

    const nextToken = source.getTokenAfter(lastElement);

    // todo: we don't know if the list is chopped or not, so we can't make any assumptions -- may produce output that will be flagged by other rules on the next run!
    // todo: we're assuming that we need trailing commas, what if we don't?
    if (nextToken !== null && nextToken.value === ',') {
      return fixer.insertTextAfter(nextToken, ` ${value},`);
    } else {
      return fixer.insertTextAfter(lastElement, `, ${value},`);
    }
  }

}

export function isLast(elementNode: TSESTree.Node): boolean {
  if (!elementNode.parent) {
    return false;
  }

  let siblingNodes: (TSESTree.Node | null)[] = [null];
  if (elementNode.parent.type === TSESTree.AST_NODE_TYPES.ArrayExpression) {
    siblingNodes = elementNode.parent.elements;
  } else if (elementNode.parent.type === TSESTree.AST_NODE_TYPES.ImportDeclaration) {
    siblingNodes = elementNode.parent.specifiers;
  }

  return elementNode === siblingNodes[siblingNodes.length - 1];
}

export function removeWithCommas(context: RuleContext<any, any>, fixer: RuleFixer, elementNode: TSESTree.Node): RuleFix[] {
  const ops = [];

  const source = getSourceCode(context);
  let nextToken = source.getTokenAfter(elementNode);
  let prevToken = source.getTokenBefore(elementNode);

  if (nextToken !== null && prevToken !== null) {
    if (nextToken.value === ',') {
      nextToken = source.getTokenAfter(nextToken);
      if (nextToken !== null) {
        ops.push(fixer.removeRange([elementNode.range[0], nextToken.range[0]]));
      }
    }
    if (isLast(elementNode) && prevToken.value === ',') {
      prevToken = source.getTokenBefore(prevToken);
      if (prevToken !== null) {
        ops.push(fixer.removeRange([prevToken.range[1], elementNode.range[1]]));
      }
    }
  } else if (nextToken !== null) {
    ops.push(fixer.removeRange([elementNode.range[0], nextToken.range[0]]));
  }

  return ops;
}

export function replaceOrRemoveArrayIdentifier(context: RuleContext<any, any>, fixer: RuleFixer, identifierNode: TSESTree.Identifier, newValue: string): RuleFix[] {
  if (identifierNode.parent.type !== TSESTree.AST_NODE_TYPES.ArrayExpression) {
    throw new Error('Parent node is not an array expression!');
  }

  const array = identifierNode.parent as TSESTree.ArrayExpression;

  for (const element of array.elements) {
    if (element !== null && element.type === TSESTree.AST_NODE_TYPES.Identifier && element.name === newValue) {
      return removeWithCommas(context, fixer, identifierNode);
    }
  }

  return [fixer.replaceText(identifierNode, newValue)];
}