/**
 * @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 { join, normalize } from '@angular-devkit/core';
import { Rule, Tree } from '@angular-devkit/schematics';
import { getWorkspacePath } from '../../utility/config';
import {
  appendPropertyInAstObject,
  findPropertyInAstObject,
  insertPropertyInAstObjectInOrder,
} from '../../utility/json-utils';
import { Builders } from '../../utility/workspace-models';
import { getTargets, getWorkspace, readJsonFileAsAstObject } from './utils';

/**
 * Updates a pre version 9 library to version 9 Ivy library.
 *
 * The main things that this migrations does are:
 * - Creates a production configuration for VE compilations.
 * - Create a prod tsconfig for which disables Ivy and enables VE compilations.
 */
export function updateLibraries(): Rule {
  return (tree, context) => {
    const logger = context.logger;
    const workspacePath = getWorkspacePath(tree);
    const workspace = getWorkspace(tree);

    const recorder = tree.beginUpdate(workspacePath);
    for (const { target, project } of getTargets(workspace, 'build', Builders.NgPackagr)) {
      const projectRoot = findPropertyInAstObject(project, 'root');
      if (!projectRoot || projectRoot.kind !== 'string') {
        break;
      }

      const configurations = findPropertyInAstObject(target, 'configurations');
      const tsConfig = join(normalize(projectRoot.value), 'tsconfig.lib.prod.json');

      if (!configurations || configurations.kind !== 'object') {
        // Configurations doesn't exist.
        appendPropertyInAstObject(recorder, target, 'configurations', { production: { tsConfig } }, 10);
        createTsConfig(tree, tsConfig);
        continue;
      }

      const prodConfig = findPropertyInAstObject(configurations, 'production');
      if (!prodConfig || prodConfig.kind !== 'object') {
        // Production configuration doesn't exist.
        insertPropertyInAstObjectInOrder(recorder, configurations, 'production', { tsConfig }, 12);
        createTsConfig(tree, tsConfig);
        continue;
      }

      const tsConfigOption = findPropertyInAstObject(prodConfig, 'tsConfig');
      if (!tsConfigOption || tsConfigOption.kind !== 'string') {
        // No tsconfig for production has been defined.
        insertPropertyInAstObjectInOrder(recorder, prodConfig, 'tsConfig', tsConfig, 14);
        createTsConfig(tree, tsConfig);
        continue;
      }

      // tsConfig for production already exists.
      const tsConfigPath = tsConfigOption.value;
      const tsConfigAst = readJsonFileAsAstObject(tree, tsConfigPath);
      if (!tsConfigAst) {
        logger.warn(`Cannot find file: ${tsConfigPath}`);
        continue;
      }

      const tsConfigRecorder = tree.beginUpdate(tsConfigPath);
      const ngCompilerOptions = findPropertyInAstObject(tsConfigAst, 'angularCompilerOptions');
      if (!ngCompilerOptions) {
        // Add angularCompilerOptions to the production tsConfig
        appendPropertyInAstObject(tsConfigRecorder, tsConfigAst, 'angularCompilerOptions', { enableIvy: false }, 2);
        tree.commitUpdate(tsConfigRecorder);
        continue;
      }

      if (ngCompilerOptions.kind === 'object') {
        const enableIvy = findPropertyInAstObject(ngCompilerOptions, 'enableIvy');
        // Add enableIvy false
        if (!enableIvy) {
          appendPropertyInAstObject(tsConfigRecorder, ngCompilerOptions, 'enableIvy', false, 4);
          tree.commitUpdate(tsConfigRecorder);
          continue;
        }

        if (enableIvy.kind !== 'false') {
          const { start, end } = enableIvy;
          tsConfigRecorder.remove(start.offset, end.offset - start.offset);
          tsConfigRecorder.insertLeft(start.offset, 'false');
          tree.commitUpdate(tsConfigRecorder);
        }
      }
    }

    tree.commitUpdate(recorder);

    return tree;
  };
}

function createTsConfig(tree: Tree, tsConfigPath: string) {
  const tsConfigContent = {
    extends: './tsconfig.lib.json',
    angularCompilerOptions: {
      enableIvy: false,
    },
  };

  if (!tree.exists(tsConfigPath)) {
    tree.create(tsConfigPath, JSON.stringify(tsConfigContent, undefined, 2));
  }
}
