/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript';

import {TypeScriptNodeEmitter} from './node_emitter';
import {GENERATED_FILES, stripComment} from './util';

function getPreamble(original: string) {
  return `*
 * @fileoverview This file was generated by the Angular template compiler. Do not edit.
 * ${original}
 * @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
 * tslint:disable
 `;
}

/**
 * Returns a transformer that does two things for generated files (ngfactory etc):
 * - adds a fileoverview JSDoc comment containing Closure Compiler specific "suppress"ions in JSDoc.
 *   The new comment will contain any fileoverview comment text from the original source file this
 *   file was generated from.
 * - updates generated files that are not in the given map of generatedFiles to have an empty
 *   list of statements as their body.
 */
export function getAngularEmitterTransformFactory(
    generatedFiles: Map<string, GeneratedFile>, program: ts.Program,
    annotateForClosureCompiler: boolean): () => (sourceFile: ts.SourceFile) => ts.SourceFile {
  return function() {
    const emitter = new TypeScriptNodeEmitter(annotateForClosureCompiler);
    return function(sourceFile: ts.SourceFile): ts.SourceFile {
      const g = generatedFiles.get(sourceFile.fileName);
      const orig = g && program.getSourceFile(g.srcFileUrl);
      let originalComment = '';
      if (orig) originalComment = getFileoverviewComment(orig);
      const preamble = getPreamble(originalComment);
      if (g && g.stmts) {
        const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, preamble);
        return newSourceFile;
      } else if (GENERATED_FILES.test(sourceFile.fileName)) {
        // The file should be empty, but emitter.updateSourceFile would still add imports
        // and various minutiae.
        // Clear out the source file entirely, only including the preamble comment, so that
        // ngc produces an empty .js file.
        const commentStmt = ts.createNotEmittedStatement(sourceFile);
        ts.addSyntheticLeadingComment(
            commentStmt, ts.SyntaxKind.MultiLineCommentTrivia, preamble,
            /* hasTrailingNewline */ true);
        return ts.updateSourceFileNode(sourceFile, [commentStmt]);
      }
      return sourceFile;
    };
  };
}

/**
 * Parses and returns the comment text (without start and end markers) of a \@fileoverview comment
 * in the given source file. Returns the empty string if no such comment can be found.
 */
function getFileoverviewComment(sourceFile: ts.SourceFile): string {
  const trivia = sourceFile.getFullText().substring(0, sourceFile.getStart());
  const leadingComments = ts.getLeadingCommentRanges(trivia, 0);
  if (!leadingComments || leadingComments.length === 0) return '';
  const comment = leadingComments[0];
  if (comment.kind !== ts.SyntaxKind.MultiLineCommentTrivia) return '';
  // Only comments separated with a \n\n from the file contents are considered file-level comments
  // in TypeScript.
  if (sourceFile.getFullText().substring(comment.end, comment.end + 2) !== '\n\n') return '';
  const commentText = sourceFile.getFullText().substring(comment.pos, comment.end);
  // Closure Compiler ignores @suppress and similar if the comment contains @license.
  if (commentText.indexOf('@license') !== -1) return '';
  // Also remove any leading `* ` from the first line in case it was a JSDOC comment
  return stripComment(commentText).replace(/^\*\s+/, '');
}
