#!/usr/bin/env node
const del = require('del')
const globby = require('globby')
const minimist = require('minimist')
const path = require('path')
const args = minimist(process.argv.slice(2), {
  string: ['exclude', 'out', 'dir'],
  boolean: [
    'help',
    'built',
    'old',
    'dry',
    'all',
    'allow-dts',
    'files',
    'quite',
    'dot',
  ],
})

const dir = path.resolve(args.dir || args._[0] || '.')
const out = args.out ? path.resolve(args.out) : dir

const help = args.help
const dot = args.dot
const quite = args.quite
const dry = args.dry
const allowDtd = args['allow-dts']
const filesList = args['files']
const prefix = args['prefix']

if (help) {
  const helpMsg = [
    'Usage example:',
    'ts-clean-built --old --dry --out out',
    'Flags:',
    '--built - for all found `.ts` files removes corresponding `.js, .d.ts, .js.map',
    '--old - will search `.d.ts` files and remove corresponding `.js, .js.map` if no `.ts/tsx` version exists',
    '--allow-dts - used with `--old`, will not remove `.d.ts` if no corresponding `.ts` or `.js` exists, allowing to have leave `.d.ts` files',
    '--all - will remove all found `.js, .d.ts, .js.map` files, **potentially dangerous** option',
    '--dot - dot-folders excluded, to include use  flag',
    '--exclude - list of patterns to exclude from search, i.e. `--exclude **/my-folder/**` will exclude all files in all directories named `my-folder` in the tree',
    '--dry - will not remove files, just show the list to going to delete',
    '--files - outputs list of files removed',
    '--quite - will not output log messages.',
    '--out - root, where to search output files, equals to `dir` by default',
    '--dir - root, where to search source (*.ts) files, is `.` (cwd) by default',
    '[dir] - last argument, the same as `--dir`',
  ].join('\n')
  console.log(helpMsg)

  process.exit()
}

const excludeFolders = ['node_modules', 'bower_components', 'jspm_packages']
const excludePatterns = excludeFolders
  .map((folder) => '!**/' + folder + '/**')
  .concat(args.exclude || [])

const log = (...args) =>
  !quite && console.log(...(prefix ? ['[ts-clean-built] '] : []), ...args)

const remove = async (patterns, opts) => {
  return await del(patterns, { ...opts, dryRun: dry, cwd: out })
}

const tsPattern = ['**/*.ts', '**/*.tsx', '!**/*.d.ts'].concat(excludePatterns)
const dtsPattern = ['**/*.d.ts'].concat(excludePatterns)
const jsPattern = ['**/*.js'].concat(excludePatterns)

const run = async () => {
  if (args.all) {
    const patterns = ['**/*.js', '**/*.js.map', '**/*.d.ts'].concat(
      excludePatterns
    )
    log('Removing ALL', '.js, .d.ts, .js.map')
    return remove(patterns, { dot })
  } else if (args.old) {
    const makeDtsMap = (files, extRegEx) => {
      return files.reduce(
        (map, tsFile) => ({
          ...map,
          [tsFile.replace(extRegEx, '.d.ts')]: true,
        }),
        {}
      )
    }

    const tsFiles = await globby(tsPattern, { dot, cwd: dir })
    const actualDtsFilesMap = makeDtsMap(tsFiles, /\.tsx?$/)

    const jsFiles = allowDtd ? await globby(jsPattern, { dot, cwd: out }) : []
    const notOrphanDtsFilesMap = makeDtsMap(jsFiles, /\.js?$/)

    const dTsFiles = await globby(dtsPattern, { dot, cwd: out })

    const notActualDts = dTsFiles.filter(
      (dtsFile) => !actualDtsFilesMap[dtsFile]
    )

    const toRemove = notActualDts
      .map((tsFile) => tsFile.replace(/\.d\.ts$/, '{.js,.js.map}'))
      .concat(
        notActualDts.filter((file) =>
          allowDtd ? notOrphanDtsFilesMap[file] : true
        )
      )
    log('Removing old', '.js, .d.ts, .js.map')
    return remove(toRemove)
  } else if (args.built) {
    const tsFiles = await globby(tsPattern, { cwd: dir })

    const toRemove = tsFiles
      .map((tsFile) => tsFile.replace(/\.tsx?$/, '{.js,.d.ts,.js.map}'))
      .concat(excludePatterns)
    log('Removing built versions .js, .d.ts, .js.map')
    return remove(toRemove)
  } else {
    console.log(
      [
        'Warning: you should provide flag which files to clean.',
        'This is to prevent unintentional deletion of files.',
        'Use --help flag to read docs about options.',
        'Use --dry flag to check desired behaviour.',
      ].join('\n')
    )
  }
}

run()
  .then((removed) => {
    if (!removed) return
    if (dry || filesList) {
      const list = removed.map((file) =>
        path.relative(dir || '.', file).replace(/\\/g, '/')
      )
      if (dry) {
        log('Dry mode, files to remove:')
        log(list)
      } else if (list.length) {
        log('List of files removed:')
        log(list)
      }
    } else {
      log('Removed', removed.length, 'files')
    }
  })
  .catch((e) => {
    console.error(e.message)
  })