import chalk from 'chalk'
import type commandDefinitions from '../command-definitions'
import * as errors from '../errors'
import * as utils from '../utils'
import { ProjectCommand } from './project-command'

export type BundleCommandDefinition = typeof commandDefinitions.bundle
export class BundleCommand extends ProjectCommand<BundleCommandDefinition> {
  public async run(buildContext?: utils.esbuild.BuildCodeContext): Promise<void> {
    const { projectType, resolveProjectDefinition } = this.readProjectDefinitionFromFS()

    const abs = this.projectPaths.abs
    const rel = this.projectPaths.rel('workDir')
    const line = this.logger.line()

    if (projectType === 'interface') {
      this.logger.success('Interface projects have no implementation to bundle.')
    } else if (projectType === 'integration') {
      const projectDef = await resolveProjectDefinition()
      const { name, __advanced } = projectDef.definition
      line.started(`Bundling integration ${chalk.bold(name)}...`)
      await this._bundle(abs.outFileCJS, buildContext, __advanced?.esbuild ?? {})
    } else if (projectType === 'bot') {
      line.started('Bundling bot...')
      await this._bundle(abs.outFileCJS, buildContext)
    } else if (projectType === 'plugin') {
      line.started('Bundling plugin for node and browser platforms...')
      await Promise.all([
        this._bundle(abs.outFileCJS, buildContext),
        this._bundle(abs.outFileESM, buildContext, { platform: 'browser', format: 'esm' }),
      ])
    } else {
      throw new errors.UnsupportedProjectType()
    }

    line.success(`Bundle available at ${chalk.grey(rel.outDir)}`)
  }

  private async _bundle(
    outfile: string,
    buildContext?: utils.esbuild.BuildCodeContext,
    props: Partial<utils.esbuild.BuildOptions> = {}
  ) {
    const abs = this.projectPaths.abs
    const buildProps = {
      outfile,
      absWorkingDir: abs.workDir,
      code: this._code,
    }
    const buildOptions = {
      ...this._buildOptions,
      ...props,
    }

    if (buildContext) {
      await buildContext.rebuild(buildProps, buildOptions)
      return
    }

    await utils.esbuild.buildCode(buildProps, buildOptions)
  }

  private get _code() {
    const rel = this.projectPaths.rel('workDir')
    const unixPath = utils.path.toUnix(rel.entryPoint)
    const importFrom = utils.path.rmExtension(unixPath)
    return `import x from './${importFrom}'; export default x; export const handler = x.handler;`
  }

  private get _buildOptions(): Partial<utils.esbuild.BuildOptions> {
    return {
      logLevel: this.argv.verbose ? 'info' : 'silent',
      sourcemap: this.argv.sourceMap,
      minify: this.argv.minify,
    }
  }
}
