/**
 * @license
 * Copyright Google Inc. 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 { EmittedFiles } from '@angular-devkit/build-webpack';
import { Path, basename, getSystemPath, join, virtualFs } from '@angular-devkit/core';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ExtraEntryPoint } from '../../../browser/schema';
import { generateEntryPoints } from '../package-chunk-sort';
import { stripBom } from '../strip-bom';
import { FileInfo, augmentIndexHtml } from './augment-index-html';

type ExtensionFilter = '.js' | '.css';

export interface WriteIndexHtmlOptions {
  host: virtualFs.Host;
  outputPath: Path;
  indexPath: Path;
  files?: EmittedFiles[];
  noModuleFiles?: EmittedFiles[];
  moduleFiles?: EmittedFiles[];
  baseHref?: string;
  deployUrl?: string;
  sri?: boolean;
  scripts?: ExtraEntryPoint[];
  styles?: ExtraEntryPoint[];
  postTransform?: IndexHtmlTransform;
}

export type IndexHtmlTransform = (content: string) => Promise<string>;

export function writeIndexHtml({
  host,
  outputPath,
  indexPath,
  files = [],
  noModuleFiles = [],
  moduleFiles = [],
  baseHref,
  deployUrl,
  sri = false,
  scripts = [],
  styles = [],
  postTransform,
}: WriteIndexHtmlOptions): Observable<void> {

  return host.read(indexPath)
    .pipe(
      map(content => stripBom(virtualFs.fileBufferToString(content))),
      switchMap(content => augmentIndexHtml({
        input: getSystemPath(outputPath),
        inputContent: content,
        baseHref,
        deployUrl,
        sri,
        entrypoints: generateEntryPoints({ scripts, styles }),
        files: filterAndMapBuildFiles(files, ['.js', '.css']),
        noModuleFiles: filterAndMapBuildFiles(noModuleFiles, '.js'),
        moduleFiles: filterAndMapBuildFiles(moduleFiles, '.js'),
        loadOutputFile: async filePath => {
          return host.read(join(outputPath, filePath))
            .pipe(
              map(data => virtualFs.fileBufferToString(data)),
            )
            .toPromise();
        },
      }),
      ),
      switchMap(content => postTransform ? postTransform(content) : of(content)),
      map(content => virtualFs.stringToFileBuffer(content)),
      switchMap(content => host.write(join(outputPath, basename(indexPath)), content)),
    );
}

function filterAndMapBuildFiles(
  files: EmittedFiles[],
  extensionFilter: ExtensionFilter | ExtensionFilter[],
): FileInfo[] {
  const filteredFiles: FileInfo[] = [];
  const validExtensions: string[] = Array.isArray(extensionFilter)
    ? extensionFilter
    : [extensionFilter];

  for (const { file, name, extension, initial } of files) {
    if (name && initial && validExtensions.includes(extension)) {
      filteredFiles.push({ file, extension, name });
    }
  }

  return filteredFiles;
}
