import path from "path"
import { initOptionsSchema } from "@/src/commands/init"
import { SHADCN_URL } from "@/src/registry/constants"
import * as ERRORS from "@/src/utils/errors"
import {
  formatMonorepoMessage,
  getMonorepoTargets,
  isMonorepoRoot,
} from "@/src/utils/get-monorepo-info"
import { getProjectInfo } from "@/src/utils/get-project-info"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import fs from "fs-extra"
import { z } from "zod"

export async function preFlightInit(
  options: z.infer<typeof initOptionsSchema>
) {
  const errors: Record<string, boolean> = {}

  // Ensure target directory exists.
  // Check for empty project. We assume if no package.json exists, the project is empty.
  if (
    !fs.existsSync(options.cwd) ||
    !fs.existsSync(path.resolve(options.cwd, "package.json"))
  ) {
    errors[ERRORS.MISSING_DIR_OR_EMPTY_PROJECT] = true
    return {
      errors,
      projectInfo: null,
    }
  }

  const projectSpinner = spinner(`Preflight checks.`, {
    silent: options.silent,
  }).start()

  if (
    fs.existsSync(path.resolve(options.cwd, "components.json")) &&
    !options.force
  ) {
    projectSpinner?.fail()
    logger.break()
    logger.error(
      `A ${highlighter.info(
        "components.json"
      )} file already exists at ${highlighter.info(
        options.cwd
      )}.\nTo start over, remove the ${highlighter.info(
        "components.json"
      )} file and run ${highlighter.info("init")} again.`
    )
    logger.break()
    process.exit(1)
  }

  projectSpinner?.succeed()

  const frameworkSpinner = spinner(`Verifying framework.`, {
    silent: options.silent,
  }).start()
  const tailwind = options.existingConfig?.tailwind as
    | Record<string, unknown>
    | undefined
  const projectInfo = await getProjectInfo(options.cwd, {
    configCssFile: typeof tailwind?.css === "string" ? tailwind.css : undefined,
  })
  if (!projectInfo || projectInfo?.framework.name === "manual") {
    errors[ERRORS.UNSUPPORTED_FRAMEWORK] = true
    frameworkSpinner?.fail()

    // Check if we're in a monorepo root.
    // Skip when --monorepo is set.
    if (!options.monorepo && (await isMonorepoRoot(options.cwd))) {
      const targets = await getMonorepoTargets(options.cwd)
      if (targets.length > 0) {
        formatMonorepoMessage("init", targets)
        process.exit(1)
      }
    }

    logger.break()
    if (projectInfo?.framework.links.installation) {
      logger.error(
        `We could not detect a supported framework at ${highlighter.info(
          options.cwd
        )}.\n` +
          `Visit ${highlighter.info(
            projectInfo?.framework.links.installation
          )} to manually configure your project.\nOnce configured, you can use the cli to add components.`
      )
    }
    logger.break()
    process.exit(1)
  }
  frameworkSpinner?.succeed(
    `Verifying framework. Found ${highlighter.info(
      projectInfo.framework.label
    )}.`
  )

  let tailwindSpinnerMessage = "Validating Tailwind CSS."

  if (projectInfo.tailwindVersion === "v4") {
    tailwindSpinnerMessage = `Validating Tailwind CSS. Found ${highlighter.info(
      "v4"
    )}.`
  }

  const tailwindSpinner = spinner(tailwindSpinnerMessage, {
    silent: options.silent,
  }).start()
  if (
    projectInfo.tailwindVersion === "v3" &&
    (!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile)
  ) {
    errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
    tailwindSpinner?.fail()
  } else if (
    projectInfo.tailwindVersion === "v4" &&
    !projectInfo?.tailwindCssFile
  ) {
    errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
    tailwindSpinner?.fail()
  } else if (!projectInfo.tailwindVersion) {
    errors[ERRORS.TAILWIND_NOT_CONFIGURED] = true
    tailwindSpinner?.fail()
  } else {
    tailwindSpinner?.succeed()
  }

  const tsConfigSpinner = spinner(`Validating import alias.`, {
    silent: options.silent,
  }).start()

  if (!projectInfo?.aliasPrefix) {
    errors[ERRORS.IMPORT_ALIAS_MISSING] = true
    tsConfigSpinner?.fail()
  } else {
    tsConfigSpinner?.succeed()
  }

  if (Object.keys(errors).length > 0) {
    if (errors[ERRORS.TAILWIND_NOT_CONFIGURED]) {
      logger.break()
      logger.error(
        `No Tailwind CSS configuration found at ${highlighter.info(
          options.cwd
        )}.`
      )
      logger.error(
        `It is likely you do not have Tailwind CSS installed or have an invalid configuration.`
      )
      logger.error(`Install Tailwind CSS then try again.`)
      if (projectInfo?.framework.links.tailwind) {
        logger.error(
          `Visit ${highlighter.info(
            projectInfo?.framework.links.tailwind
          )} to get started.`
        )
      }
    }

    if (errors[ERRORS.IMPORT_ALIAS_MISSING]) {
      logger.break()
      logger.error(
        `Could not find valid path aliases or package imports for ${highlighter.info(
          "init"
        )}.`
      )
      logger.error(
        `Configure path aliases in ${highlighter.info(
          "tsconfig.json"
        )} or imports in ${highlighter.info("package.json")}, then run ${highlighter.info(
          "init"
        )} again.`
      )
      logger.error(
        `Learn more at ${highlighter.info(
          `${SHADCN_URL}/docs/installation/manual#configure-import-aliases`
        )}.`
      )
    }

    logger.break()
    process.exit(1)
  }

  return {
    errors,
    projectInfo,
  }
}
