/**
 * @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 * as semver from 'semver';

import {ReleaseConfig} from '../config/index';

import {fetchProjectNpmPackageInfo} from './npm-registry';

/** Interface describing determined LTS branches. */
export interface LtsBranches {
  /** List of active LTS version branches. */
  active: LtsBranch[];
  /** List of inactive LTS version branches. */
  inactive: LtsBranch[];
}

/** Interface describing an LTS version branch. */
export interface LtsBranch {
  /** Name of the branch. */
  name: string;
  /** Most recent version for the given LTS branch. */
  version: semver.SemVer;
  /** NPM dist tag for the LTS version. */
  npmDistTag: string;
}

/**
 * Number of months a major version in Angular is actively supported. See:
 * https://angular.io/guide/releases#support-policy-and-schedule.
 */
const majorActiveSupportDuration = 6;

/**
 * Number of months a major version has active long-term support. See:
 * https://angular.io/guide/releases#support-policy-and-schedule.
 */
const majorLongTermSupportDuration = 12;

/** Regular expression that matches LTS NPM dist tags. */
const ltsNpmDistTagRegex = /^v(\d+)-lts$/;

/** Finds all long-term support release trains from the specified NPM package. */
export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig):
    Promise<LtsBranches> {
  const {'dist-tags': distTags, time} = await fetchProjectNpmPackageInfo(config);
  const today = new Date();
  const active: LtsBranch[] = [];
  const inactive: LtsBranch[] = [];

  // Iterate through the NPM package information and determine active/inactive LTS versions with
  // their corresponding branches. We assume that an LTS tagged version in NPM belongs to the
  // last-minor branch of a given major (i.e. we assume there are no outdated LTS NPM dist tags).
  for (const npmDistTag in distTags) {
    if (ltsNpmDistTagRegex.test(npmDistTag)) {
      const version = semver.parse(distTags[npmDistTag])!;
      const branchName = `${version.major}.${version.minor}.x`;
      const majorReleaseDate = new Date(time[`${version.major}.0.0`]);
      const ltsEndDate = computeLtsEndDateOfMajor(majorReleaseDate);
      const ltsBranch: LtsBranch = {name: branchName, version, npmDistTag};
      // Depending on whether the LTS phase is still active, add the branch
      // to the list of active or inactive LTS branches.
      if (today <= ltsEndDate) {
        active.push(ltsBranch);
      } else {
        inactive.push(ltsBranch);
      }
    }
  }

  // Sort LTS branches in descending order. i.e. most recent ones first.
  active.sort((a, b) => semver.rcompare(a.version, b.version));
  inactive.sort((a, b) => semver.rcompare(a.version, b.version));

  return {active, inactive};
}

/**
 * Computes the date when long-term support ends for a major released at the
 * specified date.
 */
export function computeLtsEndDateOfMajor(majorReleaseDate: Date): Date {
  return new Date(
      majorReleaseDate.getFullYear(),
      majorReleaseDate.getMonth() + majorActiveSupportDuration + majorLongTermSupportDuration,
      majorReleaseDate.getDate(), majorReleaseDate.getHours(), majorReleaseDate.getMinutes(),
      majorReleaseDate.getSeconds(), majorReleaseDate.getMilliseconds());
}

/** Gets the long-term support NPM dist tag for a given major version. */
export function getLtsNpmDistTagOfMajor(major: number): string {
  // LTS versions should be tagged in NPM in the following format: `v{major}-lts`.
  return `v${major}-lts`;
}
