/**
 * @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 { Path, join, normalize, dirname, NormalizedRoot } from '@angular-devkit/core';
import { DirEntry, Tree } from '@angular-devkit/schematics';

export const MODULE_EXT = '.module.ts';
export const ROUTING_MODULE_EXT = '-routing.module.ts';

/**
 * Find the module referred by a set of options passed to the schematics.
 */
export function findRootModule(host: Tree, module: string, rootPath = '', skipImport = false): string | undefined {
  if (skipImport || !module) {
    return undefined;
  }

  const modulePath = normalize(`${rootPath}/${module}`);
  if (host.exists(modulePath)) {
    return modulePath;
  } else if (host.exists(modulePath + '.ts')) {
    return normalize(modulePath + '.ts');
  } else if (host.exists(modulePath + MODULE_EXT)) {
    return normalize(modulePath + MODULE_EXT);
  } else if (host.exists(`${modulePath}/${module}${MODULE_EXT}`)) {
    return normalize(`${modulePath}/${module}${MODULE_EXT}`);
  } else if (host.exists(`${modulePath}/${module}.ts`)) {
    return normalize(`${modulePath}/${module}.ts`);
  } else {
    throw new Error(`Specified module path ${modulePath} does not exist`);
  }
}

/**
 * Find the module referred by a set of options passed to the schematics.
 */
export function findModuleFromOptions(host: Tree, options: any, projectPath: any): Path | undefined {
  if (options.hasOwnProperty('skipImport') && options.skipImport) {
    return undefined;
  }

  const moduleExt = options.moduleExt || MODULE_EXT;
  const routingModuleExt = options.routingModuleExt || ROUTING_MODULE_EXT;

  if (!options.module) {
    const pathToCheck = (projectPath || '') + '/' + options.name;
    const module = findModule(host, pathToCheck, moduleExt, routingModuleExt);
    return module ? normalize(module) : undefined;
  } else {
    const modulePath = normalize(`/${projectPath}/${options.module}`);
    const componentPath = normalize(`/${projectPath}/${options.name}`);
    const moduleBaseName = normalize(modulePath)
      .split('/')
      .pop();

    const candidateSet = new Set<Path>([normalize(projectPath || '/')]);

    for (let dir = modulePath; dir !== NormalizedRoot; dir = dirname(dir)) {
      candidateSet.add(dir);
    }
    for (let dir = componentPath; dir !== NormalizedRoot; dir = dirname(dir)) {
      candidateSet.add(dir);
    }

    const candidatesDirs = [...candidateSet].sort((a, b) => b.length - a.length);
    for (const c of candidatesDirs) {
      const candidateFiles = ['', `${moduleBaseName}.ts`, `${moduleBaseName}${moduleExt}`].map(x => join(c, x));

      for (const sc of candidateFiles) {
        if (host.exists(sc)) {
          return normalize(sc);
        }
      }
    }
    throw new Error(
      `Specified module '${options.module}' does not exist.\n` +
        `Looked in the following directories:\n    ${candidatesDirs.join('\n    ')}`
    );
  }
}

/**
 * Function to find the "closest" module to a generated file's path.
 */
export function findModule(
  host: Tree,
  generateDir: string,
  moduleExt = MODULE_EXT,
  routingModuleExt = ROUTING_MODULE_EXT
): Path {
  let dir: DirEntry | null = host.getDir('/' + generateDir);
  let foundRoutingModule = false;

  while (dir) {
    const allMatches = dir.subfiles.filter(p => p.endsWith(moduleExt));
    const filteredMatches = allMatches.filter(p => !p.endsWith(routingModuleExt));

    foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length;

    if (filteredMatches.length === 1) {
      return join(dir.path, filteredMatches[0]);
    } else if (filteredMatches.length > 1) {
      throw new Error(
        'More than one module matches. Use skip-import option to skip importing ' +
          'the component into the closest module.'
      );
    }

    dir = dir.parent;
  }

  const errorMsg = foundRoutingModule
    ? 'Could not find a non Routing NgModule.' +
      `\nModules with suffix '${routingModuleExt}' are strictly reserved for routing.` +
      '\nUse the skip-import option to skip importing in NgModule.'
    : 'Could not find an NgModule. Use the skip-import option to skip importing in NgModule.';
  throw new Error(errorMsg);
}