// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

package cjpm.command

import std.collection.*
import std.fs.*
import cjpm.implement.*
import cjpm.config.*

let INSTALL_USAGE: String = """
Install a Cangjie binary.

Usage:
  cjpm install [option]

Available option:
  -h, --help                help for install
  -V, --verbose             enable verbose
  -m, --member <value>      specify a member module of the workspace
  -g                        enable install debug version target
  --path <value>            specify path of source module, default as current path
  --root <value>            specify path of installed binary
  --git <value>             specify url of installed git module
  --branch <value>          specify branch of installed git module
  --tag <value>             specify tag of installed git module
  --commit <value>          specify commit ID of installed git module
  -j, --jobs <N>            the number of jobs to spawn in parallel during the build process
  --cfg                     enable the customized option 'cfg'
  --target-dir <value>      specify target directory
  --name <value>            specify product name to install, default to install all products
  --skip-build              install binary in target directory directly without building
  --list                    list all installed modules and their versions
  --skip-script             disable script 'build.cj'."""
let SKIP_BUILD_WARNING = "Warning: cannot skip building while installing binary from Git, " +
    "--skip-build will be ignored"
let INSTALL_TYPE_CONFLICT = "Error: cjpm install can have at most one type of configuration " +
    "between --git(from git) and --path(local project)"

class InstallCommand <: Handle {
    private let INSTALL_SHORT_OPTION: String = "Vm:gj:"
    private let INSTALL_LONG_OPTION: Array<String> = ["verbose", "member=", "path=", "root=", "git=", "branch=",
        "tag=", "commit=", "jobs=", "target-dir=", "skip-build", "list", "name=", "skip-script"]
    public var installConfig: InstallConfig = InstallConfig()

    init(args: Array<String>) {
        this.cmdName = args[0]
        this.arguments = args[1..]
    }

    public override func printHelp(): Unit {
        println(INSTALL_USAGE)
        return
    }

    public override func printPass(): Unit {
        if (!this.installConfig.list) {
            println("cjpm install success")
        }
    }

    func handleUnparseArgs(unparseArgs: Array<String>): Bool {
        if (unparseArgs.isEmpty()) {
            return true
        }

        if (unparseArgs.contains("-j") || unparseArgs.contains("--jobs")) {
            eprintln("Error: the '-j/--jobs' option requires a positive integer argument, " +
                "its valid range is (0, 2147483647]")
            return false
        }

        let options = ["--target-dir", "-m", "--member", "--path", "--root",
            "--git", "--branch", "--tag", "--commit", "--name"]
        for (val in options) {
            if (unparseArgs.contains(val)) {
                eprintln("Error: the '${val}' option needs a value")
                return false
            }
        }

        for (arg in unparseArgs) {
            if (!arg.startsWith("--")) {
                if (handleNameVersion(this.installConfig, arg)) {
                    continue
                }
                return false
            }
            let option: String = arg[2..] // remove -- prefix
            if (!customizedOptionCheck(option, "")) {
                return false
            }
            this.installConfig.customizedOption.add(option)
        }

        return true
    }

    func handleRoot(argMap: HashMap<String, String>, unparseArgs: Array<String>): Bool {
        if (unparseArgs.contains("--root")) {
            eprintln("Error: the '--root' option needs a value")
            return false
        }
        let (rootPath, rootFlag) = getArgValue(argMap, "--root")
        if (rootFlag) {
            if (rootPath.isEmpty()) {
                eprintln("Error: the '--root' option cannot be empty")
                return false
            }
            this.installConfig.root = rootPath
        } else {
            this.installConfig.root = Path(DEFAULT_CONFIG_LOCATION).join(".cjpm").toString()
        }
        return true
    }

    func handleArgs(argMap: HashMap<String, String>): Bool {
        let (path, pathFlag) = getArgValue(argMap, "--path")
        let (_, jobsFlag) = getArgValue(argMap, "-j", "--jobs")
        let (name, _) = getArgValue(argMap, "--name")
        let (git, gitFlag) = getArgValue(argMap, "--git")
        let (branch, branchFlag) = getArgValue(argMap, "--branch")
        let (tag, tagFlag) = getArgValue(argMap, "--tag")
        let (commit, commitFlag) = getArgValue(argMap, "--commit")
        this.installConfig.skipBuild = getBoolOption(argMap, "--skip-build")
        this.installConfig.verbose = getBoolOption(argMap, "-V", "--verbose")
        this.installConfig.debug = getBoolOption(argMap, "-g")
        this.installConfig.globalConfig.isSkipScript = getBoolOption(argMap, "--skip-script")

        if (!handleInstallType(pathFlag, gitFlag)) {
            return false
        }

        if (!gitFlag && (branchFlag || tagFlag || commitFlag)) {
            eprintln("Warning: --branch, --tag and --commit will be ignored without --git")
        }

        handleSkipBuild(jobsFlag)

        match (this.installConfig.installType) {
            case InstallType.Local => this.installConfig.localPath = path
            case InstallType.Git =>
                this.installConfig.gitConfig.url = git
                this.installConfig.gitConfig.branch = branch
                this.installConfig.gitConfig.tag = tag
                this.installConfig.gitConfig.commitId = commit
            case _ => ()
        }

        this.installConfig.installName = name

        return true
    }

    func handleInstallType(pathFlag: Bool, gitFlag: Bool): Bool {
        match (this.installConfig.installType) {
            case InstallType.Default =>
                if (pathFlag && gitFlag) {
                    eprintln(INSTALL_TYPE_CONFLICT)
                    return false
                }
                if (gitFlag) {
                    this.installConfig.installType = InstallType.Git
                } else {
                    this.installConfig.installType = InstallType.Local
                }
            case _ => ()
        }
        return true
    }

    func handleSkipBuild(jobsFlag: Bool): Unit {
        if (this.installConfig.skipBuild) {
            match (this.installConfig.installType) {
                case InstallType.Local =>
                    if (jobsFlag || !this.installConfig.customizedOption.isEmpty()) {
                        eprintln("Warning: configuration of building will be ignored with --skip-build")
                    }
                case InstallType.Default => ()
                case _ =>
                    eprintln(SKIP_BUILD_WARNING)
                    this.installConfig.skipBuild = false
            }
        }
    }

    public override func handleCommand(): Bool {
        let (argMap, unparseArgs) = getParseOption(this.arguments, INSTALL_SHORT_OPTION, INSTALL_LONG_OPTION)

        if (!handleRoot(argMap, unparseArgs)) {
            return false
        }

        if (getBoolOption(argMap, "--list")) {
            this.installConfig.list = true
            doInstallList(this.installConfig.root)
            return true
        }

        if (!handleUnparseArgs(unparseArgs)) {
            return false
        }

        if (!handleParallelSize(argMap)) {
            return false
        }

        if (let Some(val) <- handleMemberOption(argMap)) {
            this.installConfig.memberModule = val
        } else {
            return false
        }

        if (!handleArgs(argMap)) {
            return false
        }

        let (_, targetDirFlag) = getArgValue(argMap, "--target-dir")
        match (this.installConfig.installType) {
            case InstallType.Local =>
                if (let Some(dir) <- handleTargetDirOption(argMap)) {
                    this.installConfig.targetDir = dir
                } else {
                    return false
                }
            case InstallType.Git =>
                if (targetDirFlag) {
                    eprintln("Warning: --target-dir will be ignored with --git")
                }
            case _ => ()
        }

        return doInstall(this.installConfig)
    }
}

func handleNameVersion(_: InstallConfig, arg: String): Bool {
    eprintln("Error: unknown command '${arg}' for cjpm install")
    return false
}
