import { readFileSync } from 'node:fs'
import path from 'node:path'
import type { Command } from '@oclif/core'
import { type ModuleConfig, loadConfig } from '@vtex/fsp-config'
import type { PackageJson } from 'type-fest'

/**
 * Maps the module name to the correct npm package
 */
export const moduleCliMap: Record<string, string> = {
  checkout: '@vtex/checkout',
  discovery: '@faststore/cli',
  'sales-app': '@vtex/sales-app',
}

const moduleCLIPathMap: Record<string, string> = {
  '@vtex/checkout': '@vtex/checkout/cli',
  '@faststore/cli': '@faststore/cli',
  '@vtex/sales-app': '@vtex/sales-app/cli',
}

export const availableModules = Object.keys(moduleCliMap)

/**
 * Load modules from the config file of a given account
 * @param account Account of the config to load the modules
 * @param moduleFilterFn Filter modules to be loaded. It loads all modules by default
 */
export async function loadModules(
  account: string,
  moduleFilterFn: (module: string) => boolean = () => true
) {
  const { stores } = await loadConfig()

  const accountConfigs = stores[account]

  if (!accountConfigs) {
    const availableAccounts = Object.keys(stores).join(', ')

    throw new Error(
      `Could not find account "${account}". Found accounts: ${availableAccounts}`
    )
  }

  const modules = Object.keys(accountConfigs)
    .filter(moduleFilterFn)
    .map((module) => {
      const moduleConfig = accountConfigs[module]
      const cli = moduleConfig?.cli ?? moduleCliMap[module]

      if (!cli) {
        throw new Error('CLI not found! Provide a valid module or a CLI')
      }

      return {
        ...accountConfigs[module],
        cli,
      }
    })

  return await Promise.all(modules.map((module) => loadModule(module)))
}

async function loadModule(module: Module): Promise<LoadedModule> {
  const foundDirectory = path.join(process.cwd(), module.path)

  if (!foundDirectory) {
    throw new Error('Module not found')
  }

  const loadedCli = await load(module)

  return { ...module, loadedCli }
}

export async function load(module: Module): Promise<ModulePackageExports> {
  let rootPackageJson: PackageJson = {}

  try {
    rootPackageJson = JSON.parse(
      readFileSync(path.join(process.cwd(), 'package.json')).toString()
    )
  } catch {
    throw new Error('Could not find package.json')
  }

  if (!rootPackageJson.devDependencies?.[module.cli]) {
    throw new Error(
      `You must add ${module.cli} to your devDependencies and install it`
    )
  }

  try {
    const importedModule = await import(moduleCLIPathMap[module.cli])

    const isEsm = !!importedModule.__esModule
    return isEsm ? importedModule.default : importedModule
  } catch {
    throw new Error(`Could not import module ${module.cli}`)
  }
}

export interface CommandList {
  build: Command.Class
  dev: Command.Class
  create: Command.Class
  serve: Command.Class
  [key: string]: Command.Class | undefined
}

interface HookList {
  preDev?: () => Promise<void>
  postDev?: () => Promise<void>
  preBuild?: () => Promise<void>
  postBuild?: () => Promise<void>
  preServe?: () => Promise<void>
  postServe?: () => Promise<void>
}

export interface LoadedModule extends Module {
  loadedCli: ModulePackageExports
}

export interface ModulePackageExports {
  commands: CommandList
  hooks?: HookList
}

export interface Module extends ModuleConfig {
  cli: string
}
