#!/usr/bin/env node

import yargs from 'yargs/yargs'
import { hideBin } from 'yargs/helpers'
import chalk from 'chalk'
import { prettifyDirectory } from './prettifyDirectory.js'
import { getFiles } from './getFiles.js'
import { runNitrogen } from './nitrogen.js'
import { promises as fs } from 'fs'
import { isValidLogLevel, setLogLevel } from './Logger.js'
import { initNewNitroModule } from './init.js'

const commandName = 'nitro-codegen'

// Maximum of 100 col width
const cliWidth = Math.min(process.stdout.columns * 0.9, 100)

// Set up yargs CLI app
await yargs(hideBin(process.argv))
  .option('log-level', {
    type: 'string',
    description: 'Configures the log-level of nitrogen.',
    default: 'info',
    choices: ['debug', 'info', 'warning', 'error'],
  })
  .middleware((args) => {
    if (!isValidLogLevel(args.logLevel)) {
      throw new Error(`Invalid log-level ("${args.logLevel}")!`)
    }
    setLogLevel(args.logLevel)
  })
  // 🔥 nitrogen [path]
  .command(
    '$0 [basePath]',
    `Usage: ${chalk.bold(`${commandName} <basePath> [options]`)}\n` +
      `Run the nitro code-generator on all ${chalk.underline('**/*.nitro.ts')} files found ` +
      `in the current directory and generate C++, Swift or Kotlin outputs in ${chalk.underline('./nitrogen/generated')}.`,
    (y) =>
      y
        .positional('basePath', {
          type: 'string',
          description: `The base path of where Nitrogen will start looking for ${chalk.underline('**/*.nitro.ts')} specs.`,
          default: process.cwd(),
        })
        .option('out', {
          type: 'string',
          description:
            'Configures the output path of the generated C++, Swift or Kotlin files.',
          default: './nitrogen/generated',
        })
        .option('config', {
          type: 'string',
          description: `A custom path to a ${chalk.underline('nitro.json')} config file.`,
          default: './nitro.json',
        }),
    async (argv) => {
      const basePath = argv.basePath
      const outputDirectory = argv.out
      await runNitrogenCommand(basePath, outputDirectory)
    }
  )
  // 🔥 nitrogen init <moduleName>
  .command(
    'init <moduleName>',
    `Usage: ${chalk.bold(`${commandName} init <moduleName> [options]`)}\n` +
      `Create a new Nitro Module.`,
    (y) =>
      y
        .positional('moduleName', {
          type: 'string',
          description: 'The name of the Nitro Module that will be created.',
          demandOption: true,
        })
        .option('path', {
          type: 'string',
          description: `A custom path to create the new Nitro Module in - instead of the current working directory.`,
          default: process.cwd(),
        }),
    async (argv) => {
      await initNewNitroModule(argv.path, argv.moduleName)
    }
  )
  .usage(
    `Usage: ${chalk.bold('$0 [options]')}\n` +
      `$0 is a code-generater for Nitro Modules (${chalk.underline('https://github.com/mrousavy/nitro')})\n` +
      `It converts all TypeScript specs found in ${chalk.underline('**/*.nitro.ts')} to C++, Swift or Kotlin specs.\n` +
      `Each library/module must have a ${chalk.underline('nitro.json')} configuration file in it's root directory.`
  )
  .help()
  .strict()
  .wrap(cliWidth).argv

async function runNitrogenCommand(
  baseDirectory: string,
  outputDirectory: string
): Promise<void> {
  // 1. Prepare output folders
  const filesBefore = await getFiles(outputDirectory)

  const start = performance.now()

  // 2. Run Nitrogen
  const { generatedFiles, generatedSpecsCount, targetSpecsCount } =
    await runNitrogen({
      baseDirectory: baseDirectory,
      outputDirectory: outputDirectory,
    })

  const end = performance.now()
  const timeS = ((end - start) / 1000).toFixed(1)
  console.log(
    `🎉  Generated ${generatedSpecsCount}/${targetSpecsCount} HybridObject${generatedSpecsCount === 1 ? '' : 's'} in ${timeS}s!`
  )
  console.log(
    `💡  Your code is in ${chalk.underline(prettifyDirectory(outputDirectory))}`
  )

  // 3. Delete all old dangling files
  const addedFiles = generatedFiles.filter((f) => !filesBefore.includes(f))
  const removedFiles = filesBefore.filter((f) => !generatedFiles.includes(f))
  if (addedFiles.length > 0 || removedFiles.length > 0) {
    let text = ''
    const as = addedFiles.length > 1 ? 's' : ''
    const rs = removedFiles.length > 1 ? 's' : ''
    if (addedFiles.length > 0 && removedFiles.length === 0) {
      text = `Added ${addedFiles.length} file${as}`
    } else if (addedFiles.length === 0 && removedFiles.length > 0) {
      text = `Removed ${removedFiles.length} file${rs}`
    } else {
      text = `Added ${addedFiles.length} file${as} and removed ${removedFiles.length} file${rs}`
    }

    console.log(
      `‼️   ${text} - ${chalk.bold('you need to run `pod install`/sync gradle to update files!')}`
    )
  }
  const promises = removedFiles.map(async (file) => {
    const stat = await fs.stat(file)
    if (stat.isFile()) {
      await fs.rm(file)
    }
  })
  await Promise.all(promises)
}
