import * as depcheck from 'depcheck';

// Ignore packages that are defined here per package.
// Note: If @babel/parser fails to parse a file, then its imports will not be detected.
const IGNORE_MATCHES = {
  '*': ['@nrwl/devkit', '@nrwl/workspace', 'chalk', 'tslib', '@swc/helpers'],
  angular: ['@angular-devkit/schematics', '@schematics/angular', 'http-server'],
  cli: [],
  cypress: [],
  devkit: [],
  'eslint-plugin-nx': [],
  jest: [
    // This is used for the type import only, we should remove it.
    'jest-resolve',
  ],
  linter: [],
  nest: ['@nestjs/schematics'],
  next: [],
  node: [],
  'nx-plugin': [],
  react: [],
  rollup: [],
  storybook: [],
  nx: ['glob'],
  vite: [],
  web: ['http-server'],
  webpack: [
    // These are not being picked up because @babel/parser is failing on the files that import them.
    'css-loader',
    'style-loader',
    'ts-loader',
    'webpack-merge',
  ],
  workspace: [
    '@parcel/watcher',
    'cli-cursor',
    'cli-spinners',
    'dotenv',
    'figures',
    'flat',
    'minimatch',
    'npm-run-path',
    'open',
    'tmp',
    'yargs',
  ],
  'make-angular-cli-faster': ['tmp', 'yargs'],
};

export default async function getUnusedDependencies(
  name: string,
  path: string,
  dependencies: JSON,
  verbose: boolean
) {
  const options: any = {
    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) => {
          switch (node.type) {
            case 'ImportDeclaration':
              return node.source &&
                node.source.value &&
                node.importKind !== 'type'
                ? [node.source.value]
                : [];
            case 'CallExpression':
              return (node.callee?.name === 'require' ||
                (node.callee?.object?.name === 'require' &&
                  node.callee?.property?.name === 'resolve')) &&
                node.arguments[0]?.value
                ? [node.arguments[0].value]
                : [];
            default:
              return [];
          }
        };
        return customImportDeclarationDetector;
      }),
    ],
    skipMissing: false, // skip calculation of missing dependencies
    ignorePatterns: [
      '*.d.ts',
      '.eslintrc.json',
      '*.spec*',
      'src/schematics/**/files/**',
    ],
  };
  let { dependencies: unused } = await depcheck(path, {
    ...options,
    package: { dependencies },
  });

  const unusedPackages = unused.filter(
    (m) =>
      !IGNORE_MATCHES['*'].includes(m) &&
      !(IGNORE_MATCHES[name] || []).includes(m)
  );

  return unusedPackages;
}
