import * as depcheck from 'depcheck';
import { join } from 'path';

// Ignore packages that are defined here per package
const IGNORE_MATCHES_IN_PACKAGE = {
  '*': [
    'nx',
    'prettier',
    'typescript',
    'rxjs',
    '@nrwl/cli',
    '@nrwl/workspace',
    // These are installed as needed and should not be added to package.json
    '@nrwl/cypress',
    '@nrwl/jest',
    '@nrwl/rollup',
    '@nrwl/storybook',
    '@nrwl/vite',
    '@nrwl/webpack',
  ],
  angular: [
    '@angular-devkit/architect',
    '@angular-devkit/build-angular',
    '@angular-devkit/core',
    '@angular/compiler',
    '@angular/compiler-cli',
    '@angular/core',
    '@angular/router',
    '@ngrx/effects',
    '@ngrx/router-store',
    '@ngrx/store',
    '@storybook/angular',
    '@module-federation/node',
    'rxjs',
    'semver',
    // installed dynamically by the library generator
    'ng-packagr',
    // ng-packagr deps, some are handled if not installed
    'injection-js',
    'browserslist',
    'cacache',
    'find-cache-dir',
    'less',
    'node-sass',
    'node-sass-tilde-importer',
    'ora',
    'convert-source-map',
    'postcss',
    'postcss-import',
    'postcss-preset-env',
    'postcss-url',
    'sass',
    'stylus',
    'tailwindcss',
  ],
  cli: ['nx'],
  cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress', 'vite'],
  devkit: ['@angular-devkit/architect', 'rxjs', 'webpack'],
  'eslint-plugin-nx': ['@angular-eslint/eslint-plugin'],
  jest: [
    'jest',
    '@jest/types',
    'identity-obj-proxy',
    '@angular-devkit/schematics',
  ],
  linter: [
    'eslint',
    '@angular-devkit/schematics',
    '@angular-devkit/architect',
    // Installed and uninstalled dynamically when the conversion generator runs
    'tslint-to-eslint-config',
    // Resolved from the end user's own workspace installation dynamically
    '@typescript-eslint/eslint-plugin',
  ],
  next: [
    '@angular-devkit/architect',
    '@nrwl/devkit',
    'express',
    'http-proxy-middleware',
    'next',
    'rxjs',
    'tsconfig-paths-webpack-plugin',
    'webpack',
  ],
  react: [
    // These are brought in by the webpack, rollup, or vite packages via init generators.
    '@babel/preset-react',
    '@module-federation/node',
    '@phenomnomnominal/tsquery',
    '@pmmmwh/react-refresh-webpack-plugin',
    '@svgr/rollup',
    '@rollup/plugin-url',
    '@svgr/webpack',
    '@swc/jest',
    'babel-jest',
    'babel-loader',
    'babel-plugin-emotion',
    'babel-plugin-styled-components',
    'css-loader',
    'file-loader',
    'less-loader',
    'react-refresh',
    'rollup',
    'sass',
    'sass-loader',
    'style-loader',
    'stylus-loader',
    'swc-loader',
    'tsconfig-paths-webpack-plugin',
    'url-loader',
    'webpack',
    'webpack-merge',
  ],
  rollup: ['@swc/core'],
  storybook: [
    '@angular-devkit/architect',
    '@angular-devkit/core',
    '@angular-devkit/schematics',
    '@storybook/addon-knobs',
    '@storybook/addon-essentials',
    '@storybook/core',
    '@storybook/core-server',
    '@storybook/types',
    'rxjs',
  ],
  nx: [
    '@angular-devkit/build-angular',
    '@angular-devkit/schematics',
    '@angular-devkit/core',
    '@angular-devkit/architect',
    '@angular/cli',
    '@nrwl/angular',
    '@nestjs/cli', // nx init nest makes use of nestjs cli (which should be available in NestJS CLI app) to parse the nest-cli.json file
    'ts-node', // We *may* fall back on ts-node, but we want to encourage the use of @swc-node instead so we don't explicitly list ts-node as an optional dep
    '@nrwl/nx-android-arm-eabi', // native optional deps
    '@nrwl/nx-android-arm64', // native optional deps
    '@nrwl/nx-darwin-arm64', // native optional deps
    '@nrwl/nx-darwin-universal', // native optional deps
    '@nrwl/nx-darwin-x64', // native optional deps
    '@nrwl/nx-freebsd-x64', // native optional deps
    '@nrwl/nx-linux-arm-gnueabihf', // native optional deps
    '@nrwl/nx-linux-arm64-gnu', // native optional deps
    '@nrwl/nx-linux-arm64-musl', // native optional deps
    '@nrwl/nx-linux-x64-gnu', // native optional deps
    '@nrwl/nx-linux-x64-musl', // native optional deps
    '@nrwl/nx-win32-arm64-msvc', // native optional deps
    '@nrwl/nx-win32-ia32-msvc', // native optional deps
    '@nrwl/nx-win32-x64-msvc', // native optional deps
    'memfs', // used in mock for handling .node files in tests
  ],
  web: [
    // we don't want to bloat the install of @nrwl/web by including @swc/core and swc-loader as a dependency.
    '@swc/core',
    'swc-loader',

    'fibers',
    'node-sass',
  ],
  webpack: ['@swc/core', 'style-loader', 'swc-loader'],
  workspace: [
    'tslint',
    '@angular-devkit/architect',
    '@angular-devkit/core',
    '@angular-devkit/schematics',
    'webpack',
    'webpack-dev-server',
    '@nrwl/cli',
    '@nrwl/jest',
    '@nrwl/linter',
    'tsconfig-paths',
  ],
  nest: ['semver'],
  'make-angular-cli-faster': ['@angular/core'],
};

const IGNORE_MATCHES_BY_FILE: Record<string, string[]> = {
  '@storybook/core': [
    join(
      __dirname,
      '../../packages/angular/src/migrations/update-12-3-0/update-storybook.ts'
    ),
  ],
};

export default async function getMissingDependencies(
  name: string,
  path: string,
  dependencies: JSON,
  verbose: boolean
) {
  const options: any = {
    /**
     * If a dependency is exclusively used via a TypeScript type import
     * e.g. `import type { Foo } from 'bar';`
     * ...then we do not want it to trigger a missing dependency warning
     * because it is not required at runtime.
     *
     * We can achieve this by overriding the default detector for
     * ImportDeclaration nodes to check the `importKind` value.
     */
    detectors: [
      ...Object.entries(depcheck.detector).map(([detectorName, detectorFn]) => {
        // Use all the default detectors, apart from 'importDeclaration'
        if (detectorName !== 'importDeclaration') {
          return detectorFn;
        }
        const customImportDeclarationDetector: depcheck.Detector = (node) => {
          return node.type === 'ImportDeclaration' &&
            node.source &&
            node.source.value &&
            node.importKind !== 'type'
            ? [node.source.value]
            : [];
        };
        return customImportDeclarationDetector;
      }),
    ],
    skipMissing: false, // skip calculation of missing dependencies
    ignorePatterns: [
      '*.d.ts',
      '.eslintrc.json',
      '*.spec*',
      'src/schematics/**/files/**',
    ],
  };
  let { missing } = await depcheck(path, {
    ...options,
    package: { dependencies },
  });

  const packagesMissing = Object.keys(missing).filter(
    (m) =>
      !IGNORE_MATCHES_IN_PACKAGE['*'].includes(m) &&
      !(IGNORE_MATCHES_IN_PACKAGE[name] || []).includes(m) &&
      missing[m].filter(
        (occurence) => !IGNORE_MATCHES_BY_FILE[m]?.includes(occurence)
      ).length
  );

  if (verbose) {
    console.log(`> ${name}`);
    packagesMissing.map((p) => {
      console.log(p, missing[p]);
    });
  }

  return packagesMissing;
}
