import {Args, Command, Flags} from '@oclif/core'
import {loadConfig, validateConfig} from '../helpers/config.js'
import {existsSync} from 'node:fs'
import {mkdir, writeFile} from 'node:fs/promises'
import {join} from 'node:path'
import {getAvailableComponentNames, getComponentRealname, getComponentURL, getRegistry} from '../helpers/registry.js'
import ora from 'ora'
import React, {ComponentPropsWithoutRef, useState} from 'react'
import {render, Box} from 'ink'
import {SearchBox} from '../components/SearchBox.js'
import {getComponentsInstalled} from '../helpers/path.js'
import {Choice} from '../components/Choice.js'
import {colorize} from '@oclif/core/ux'

function Generator() {
  let complete: boolean = false

  function ComponentSelector<T extends {displayName: string; key: string; installed: boolean}>(
    props: Omit<ComponentPropsWithoutRef<typeof SearchBox<T>>, 'helper' | 'onSubmit'> & {
      installed: string[]
      onComplete: (value: T, force: 'yes' | 'no' | null) => void
      skipForce: boolean
    },
  ) {
    const [forceStaged, setForceStaged] = useState<boolean>(false)
    const [onCompleteCache, setCache] = useState<T>({key: '', displayName: '', installed: false} as T)

    return (
      <Box>
        {!forceStaged ? (
          <SearchBox
            helper={'Press Enter to select component.'}
            {...props}
            onSubmit={(T) => {
              if (T.installed && !props.skipForce) {
                setForceStaged(true)
                setCache(T)
              } else {
                complete = true
                props.onComplete(T, null)
              }
            }}
          />
        ) : null}
        {forceStaged ? (
          <Choice
            question={'You already installed this component. Overwrite?'}
            yes={'Yes, overwrite existing file and install it.'}
            no={'No, cancel the action.'}
            onSubmit={(value) => {
              complete = true
              props.onComplete(onCompleteCache, value)
            }}
          />
        ) : null}
      </Box>
    )
  }

  return [
    ComponentSelector,
    new Promise<void>((r) => {
      const i = setInterval(() => {
        if (complete) {
          r()
          clearInterval(i)
        }
      }, 100)
    }),
  ] as const
}

export default class Add extends Command {
  static override args = {
    name: Args.string({description: 'name of component to install'}),
  }

  static override description = 'Add a component to the project.'

  static override examples = ['<%= config.bin %> <%= command.id %>']

  static override flags = {
    force: Flags.boolean({char: 'f', description: 'override the existing file'}),
    // WARNING: forceShared could break your components!
    forceShared: Flags.boolean({char: 'F', description: 'override the existing shared.ts and update it to latest'}),
    config: Flags.string({char: 'p', description: 'path to config'}),
    shared: Flags.string({char: 's', description: 'place for installation of shared.ts'}),
    components: Flags.string({char: 'c', description: 'place for installation of components'}),
  }

  public async run(): Promise<void> {
    let {
      args,
      flags: {force, ...flags},
    } = await this.parse(Add)

    const resolvedConfig = await validateConfig((message: string) => this.log(message), await loadConfig(flags.config))
    const componentFolder = join(process.cwd(), resolvedConfig.paths.components)
    const sharedFile = join(process.cwd(), resolvedConfig.paths.shared)
    if (!existsSync(componentFolder)) {
      await mkdir(componentFolder, {recursive: true})
    }

    const loadRegistryOra = ora('Fetching registry...').start()
    const unsafeRegistry = await getRegistry()
    if (!unsafeRegistry.ok) {
      loadRegistryOra.fail(unsafeRegistry.message)
      return
    }
    const registry = unsafeRegistry.registry
    const componentNames = await getAvailableComponentNames(registry)
    loadRegistryOra.succeed(`Successfully fetched registry! (${componentNames.length} components)`)
    const componentRealNames = await Promise.all(
      componentNames.map(async (name) => await getComponentRealname(registry, name)),
    )
    const installed = await getComponentsInstalled(componentRealNames, resolvedConfig)
    const searchBoxComponent = componentNames.map((name, index) => ({
      displayName: installed.includes(componentRealNames[index]) ? `${name} (installed)` : name,
      key: name,
      installed: installed.includes(componentRealNames[index]),
    }))

    let name: string | undefined = args.name?.toLowerCase?.()
    let quit = false

    if (!name || !componentNames.includes(name.toLowerCase())) {
      const [ComponentSelector, waitForComplete] = Generator()

      const inkInstance = render(
        <ComponentSelector
          components={searchBoxComponent}
          initialQuery={args.name}
          installed={installed}
          skipForce={force}
          onComplete={(comp, forceSelected) => {
            name = comp.key
            force = forceSelected === 'yes'
            quit = forceSelected === 'no'
            inkInstance.clear()
          }}
        />,
      )
      await waitForComplete
      inkInstance.unmount()
      if (quit) {
        this.log(colorize('redBright', 'Installation canceled by user.'))
        return
      }
    }

    if (!name) {
      this.error('Component name is not provided, or not selected.')
    }

    const sharedFileOra = ora('Installing shared module...').start()
    if (!existsSync(sharedFile) || flags.forceShared) {
      const sharedFileContentResponse = await fetch(registry.shared)
      if (!sharedFileContentResponse.ok) {
        sharedFileOra.fail(
          `Error while fetching shared module content: ${sharedFileContentResponse.status} ${sharedFileContentResponse.statusText}`,
        )
        return
      }
      const sharedFileContent = await sharedFileContentResponse.text()
      await writeFile(sharedFile, sharedFileContent)
      sharedFileOra.succeed('Shared module is successfully installed!')
    } else {
      sharedFileOra.succeed('Shared module is already installed!')
    }

    const componentFileOra = ora(`Installing ${name} component...`).start()
    const componentFile = join(componentFolder, registry.components[name])
    if (existsSync(componentFile) && !force) {
      componentFileOra.succeed(`Component is already installed! (${componentFile})`)
    } else {
      const componentFileContentResponse = await fetch(await getComponentURL(registry, name))
      if (!componentFileContentResponse.ok) {
        componentFileOra.fail(
          `Error while fetching component file content: ${componentFileContentResponse.status} ${componentFileContentResponse.statusText}`,
        )
        return
      }
      const componentFileContent = (await componentFileContentResponse.text()).replaceAll(
        /import\s+{[^}]*}\s+from\s+"..\/shared"/g,
        (match) => match.replace(/..\/shared/, resolvedConfig.import.shared),
      )
      await writeFile(componentFile, componentFileContent)
      componentFileOra.succeed('Component is successfully installed!')
    }

    this.log('Now you can import the component.')
  }
}
