import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import {
  chain,
  Rule,
  SchematicContext,
  Tree,
} from '@angular-devkit/schematics';
import {
  addDepsToPackageJson,
  readJsonInTree,
  updateJsonInTree,
} from '@nrwl/workspace';
import { noop } from 'rxjs';
import {
  babelCoreVersion,
  babelJestVersion,
  babelPresetEnvVersion,
  babelPresetReactVersion,
  babelPresetTypescriptVersion,
  jestTypesVersion,
  jestVersion,
  nxVersion,
  tsJestVersion,
} from '../../utils/versions';
import { JestInitSchema } from './schema';

interface NormalizedSchema extends ReturnType<typeof normalizeOptions> {}

const schemaDefaults = {
  babelJest: false,
} as const;

const removeNrwlJestFromDeps = (host: Tree) => {
  // check whether updating the package.json is necessary
  const currentPackageJson = readJsonInTree(host, 'package.json');

  if (
    currentPackageJson.dependencies &&
    currentPackageJson.dependencies['@nrwl/jest']
  ) {
    return updateJsonInTree('package.json', (json) => {
      json.dependencies = json.dependencies || {};
      delete json.dependencies['@nrwl/jest'];
      return json;
    });
  } else {
    return noop();
  }
};

const createJestConfig = (host: Tree) => {
  if (!host.exists('jest.config.js')) {
    host.create(
      'jest.config.js',
      stripIndents`
  module.exports = {
    projects: []
  };`
    );
  }

  if (!host.exists('jest.preset.js')) {
    host.create(
      'jest.preset.js',
      `
      const nxPreset = require('@nrwl/jest/preset');
     
      module.exports = { ...nxPreset }`
    );
  }
};

function updateDependencies(options: NormalizedSchema): Rule {
  const devDeps = {
    '@nrwl/jest': nxVersion,
    jest: jestVersion,
    '@types/jest': jestTypesVersion,
    'ts-jest': tsJestVersion,
  };

  if (options.babelJest) {
    devDeps['@babel/core'] = babelCoreVersion;
    devDeps['@babel/preset-env'] = babelPresetEnvVersion;
    devDeps['@babel/preset-typescript'] = babelPresetTypescriptVersion;
    devDeps['@babel/preset-react'] = babelPresetReactVersion;
    devDeps['babel-jest'] = babelJestVersion;
  }

  return addDepsToPackageJson({}, devDeps);
}

function updateExtensions(host: Tree, context: SchematicContext) {
  if (!host.exists('.vscode/extensions.json')) {
    return;
  }

  return updateJsonInTree('.vscode/extensions.json', (json) => {
    json.recommendations = json.recommendations || [];
    const extension = 'firsttris.vscode-jest-runner';
    if (!json.recommendations.includes(extension)) {
      json.recommendations.push(extension);
    }
    return json;
  })(host, context);
}

export default function (schema: JestInitSchema): Rule {
  const options = normalizeOptions(schema);

  return chain([
    createJestConfig,
    updateDependencies(options),
    removeNrwlJestFromDeps,
    updateExtensions,
  ]);
}

function normalizeOptions(options: JestInitSchema) {
  return {
    ...schemaDefaults,
    ...options,
  };
}
