//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

import ArgumentParser
import ContainerizationError
import Foundation

public struct Flags {
    public struct Logging: ParsableArguments {
        public init() {}

        public init(debug: Bool) {
            self.debug = debug
        }

        @Flag(name: .long, help: "Enable debug output [environment: CONTAINER_DEBUG]")
        public var debug = false
    }

    public struct Process: ParsableArguments {
        public init() {}

        public init(
            cwd: String?,
            env: [String],
            envFile: [String],
            gid: UInt32?,
            interactive: Bool,
            tty: Bool,
            uid: UInt32?,
            ulimits: [String],
            user: String?
        ) {
            self.cwd = cwd
            self.env = env
            self.envFile = envFile
            self.gid = gid
            self.interactive = interactive
            self.tty = tty
            self.uid = uid
            self.ulimits = ulimits
            self.user = user
        }

        @Option(name: .shortAndLong, help: "Set environment variables (key=value, or just key to inherit from host)")
        public var env: [String] = []

        @Option(
            name: .long,
            help: "Read in a file of environment variables (key=value format, ignores # comments and blank lines)"
        )
        public var envFile: [String] = []

        @Option(name: .long, help: "Set the group ID for the process")
        public var gid: UInt32?

        @Flag(name: .shortAndLong, help: "Keep the standard input open even if not attached")
        public var interactive = false

        @Flag(name: .shortAndLong, help: "Open a TTY with the process")
        public var tty = false

        @Option(name: .shortAndLong, help: "Set the user for the process (format: name|uid[:gid])")
        public var user: String?

        @Option(name: .long, help: "Set the user ID for the process")
        public var uid: UInt32?

        @Option(
            name: [.customShort("w"), .customLong("workdir"), .long],
            help: .init(
                "Set the initial working directory inside the container",
                valueName: "dir"
            )
        )
        public var cwd: String?

        @Option(
            name: .customLong("ulimit"),
            help: .init(
                "Set resource limits (format: <type>=<soft>[:<hard>])",
                valueName: "limit"
            )
        )
        public var ulimits: [String] = []
    }

    public struct Resource: ParsableArguments {
        public init() {}

        public init(cpus: Int64?, memory: String?) {
            self.cpus = cpus
            self.memory = memory
        }

        @Option(name: .shortAndLong, help: "Number of CPUs to allocate to the container")
        public var cpus: Int64?

        @Option(
            name: .shortAndLong,
            help: "Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix"
        )
        public var memory: String?
    }

    public struct DNS: ParsableArguments {
        public init() {}

        public init(domain: String?, nameservers: [String], options: [String], searchDomains: [String]) {
            self.domain = domain
            self.nameservers = nameservers
            self.options = options
            self.searchDomains = searchDomains
        }

        @Option(
            name: .customLong("dns"),
            help: .init("DNS nameserver IP address", valueName: "ip")
        )
        public var nameservers: [String] = []

        @Option(
            name: .customLong("dns-domain"),
            help: .init("Default DNS domain", valueName: "domain")
        )
        public var domain: String? = nil

        @Option(
            name: .customLong("dns-option"),
            help: .init("DNS options", valueName: "option")
        )
        public var options: [String] = []

        @Option(
            name: .customLong("dns-search"),
            help: .init("DNS search domains", valueName: "domain")
        )
        public var searchDomains: [String] = []
    }

    public struct Registry: ParsableArguments {
        public init() {}

        public init(scheme: String) {
            self.scheme = scheme
        }

        @Option(help: "Scheme to use when connecting to the container registry. One of (http, https, auto)")
        public var scheme: String = "auto"
    }

    public struct Management: ParsableArguments {
        public init() {}

        public init(
            arch: String,
            capAdd: [String],
            capDrop: [String],
            cidfile: String,
            detach: Bool,
            dns: Flags.DNS,
            dnsDisabled: Bool,
            entrypoint: String?,
            initImage: String?,
            kernel: String?,
            kernelArgs: [String],
            labels: [String],
            maskedPaths: [String],
            mounts: [String],
            name: String?,
            networks: [String],
            os: String,
            platform: String?,
            publishPorts: [String],
            publishSockets: [String],
            readOnly: Bool,
            readonlyPaths: [String],
            remove: Bool,
            rosetta: Bool,
            runtime: String?,
            ssh: Bool,
            shmSize: String?,
            tmpFs: [String],
            useInit: Bool,
            virtualization: Bool,
            volumes: [String]
        ) {
            self.arch = arch
            self.capAdd = capAdd
            self.capDrop = capDrop
            self.cidfile = cidfile
            self.detach = detach
            self.dns = dns
            self.dnsDisabled = dnsDisabled
            self.entrypoint = entrypoint
            self.initImage = initImage
            self.kernel = kernel
            self.kernelArgs = kernelArgs
            self.labels = labels
            self.maskedPaths = maskedPaths
            self.mounts = mounts
            self.name = name
            self.networks = networks
            self.os = os
            self.platform = platform
            self.publishPorts = publishPorts
            self.publishSockets = publishSockets
            self.readOnly = readOnly
            self.readonlyPaths = readonlyPaths
            self.remove = remove
            self.rosetta = rosetta
            self.runtime = runtime
            self.ssh = ssh
            self.shmSize = shmSize
            self.tmpFs = tmpFs
            self.useInit = useInit
            self.virtualization = virtualization
            self.volumes = volumes
        }

        @Option(name: .shortAndLong, help: "Set arch if image can target multiple architectures")
        public var arch: String = Arch.hostArchitecture().rawValue

        @Option(
            name: .customLong("cap-add"),
            help: .init("Add a Linux capability (e.g. CAP_NET_RAW, or ALL)", valueName: "cap")
        )
        public var capAdd: [String] = []

        @Option(
            name: .customLong("cap-drop"),
            help: .init("Drop a Linux capability (e.g. CAP_NET_RAW, or ALL)", valueName: "cap")
        )
        public var capDrop: [String] = []

        @Option(name: .long, help: "Write the container ID to the path provided")
        public var cidfile = ""

        @Flag(name: .shortAndLong, help: "Run the container and detach from the process")
        public var detach = false

        @OptionGroup
        public var dns: Flags.DNS

        @Option(
            name: .long,
            help: .init(
                "Override the entrypoint of the image",
                valueName: "cmd"
            )
        )
        public var entrypoint: String?

        @Flag(name: .customLong("init"), help: "Run an init process inside the container that forwards signals and reaps processes")
        public var useInit = false

        @Option(
            name: .long,
            help: .init("Use a custom init image instead of the default", valueName: "image")
        )
        public var initImage: String?

        @Option(
            name: .shortAndLong,
            help: .init("Set a custom kernel path", valueName: "path"),
            completion: .file(),
            transform: { str in
                URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false)
            }
        )
        public var kernel: String?

        @Option(
            name: .customLong("kernel-arg"),
            help: .init(
                "Append a raw boot argument to the kernel command line (repeatable).",
                valueName: "arg"
            )
        )
        public var kernelArgs: [String] = []

        @Option(name: [.short, .customLong("label")], help: "Add a key=value label to the container")
        public var labels: [String] = []

        /// EXPERIMENTAL: The flag is subject to change.
        @Option(
            name: .customLong("masked-path"),
            help: .init(
                "[EXPERIMENTAL] Hide a path inside the container, in addition to the runtime defaults (or NONE to clear prior values and the defaults)",
                valueName: "path"
            )
        )
        public var maskedPaths: [String] = []

        @Option(name: .customLong("mount"), help: "Add a mount to the container (format: type=<>,source=<>,target=<>,readonly)")
        public var mounts: [String] = []

        @Option(name: .long, help: "Use the specified name as the container ID")
        public var name: String?

        @Option(name: [.customLong("network")], help: "Attach the container to a network (format: <name>[,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE])")
        public var networks: [String] = []

        @Flag(name: [.customLong("no-dns")], help: "Do not configure DNS in the container")
        public var dnsDisabled = false

        @Option(name: .long, help: "Set OS if image can target multiple operating systems")
        public var os = "linux"

        @Option(
            name: [.customShort("p"), .customLong("publish")],
            help: .init(
                "Publish a port from container to host (format: [host-ip:]host-port:container-port[/protocol])",
                valueName: "spec"
            )
        )
        public var publishPorts: [String] = []

        @Option(name: .long, help: "Platform for the image if it's multi-platform. This takes precedence over --os and --arch [environment: CONTAINER_DEFAULT_PLATFORM]")
        public var platform: String?

        @Option(
            name: .customLong("publish-socket"),
            help: .init(
                "Publish a socket from container to host (format: host_path:container_path)",
                valueName: "spec"
            )
        )
        public var publishSockets: [String] = []

        @Flag(name: .long, help: "Mount the container's root filesystem as read-only")
        public var readOnly = false

        /// EXPERIMENTAL: The flag is subject to change.
        @Option(
            name: .customLong("read-only-path"),
            help: .init(
                "[EXPERIMENTAL] Mark a path inside the container read-only, in addition to the runtime defaults (or NONE to clear prior values and the defaults)",
                valueName: "path"
            )
        )
        public var readonlyPaths: [String] = []

        @Flag(name: [.customLong("rm"), .long], help: "Remove the container after it stops")
        public var remove = false

        @Flag(name: .long, help: "Enable Rosetta in the container")
        public var rosetta = false

        @Option(name: .long, help: "Set the runtime handler for the container (default: container-runtime-linux)")
        public var runtime: String?

        @Flag(name: .long, help: "Forward SSH agent socket to container")
        public var ssh = false

        @Option(name: .customLong("shm-size"), help: "Size of /dev/shm (e.g. 64M, 1G)")
        public var shmSize: String?

        @Option(name: .customLong("tmpfs"), help: "Add a tmpfs mount to the container at the given path")
        public var tmpFs: [String] = []

        @Flag(
            name: .long,
            help:
                "Expose virtualization capabilities to the container (requires host and guest support)"
        )
        public var virtualization: Bool = false

        @Option(name: [.customLong("volume"), .short], help: "Bind mount a volume into the container")
        public var volumes: [String] = []

        public func validate() throws {
            if dnsDisabled {
                let hasDNSConfig =
                    !dns.nameservers.isEmpty
                    || dns.domain != nil
                    || !dns.options.isEmpty
                    || !dns.searchDomains.isEmpty
                if hasDNSConfig {
                    throw ValidationError(
                        "`--no-dns` cannot be used with DNS configuration flags (`--dns`, `--dns-domain`, `--dns-option`, `--dns-search`)"
                    )
                }
            }
        }
    }

    public struct Progress: ParsableArguments {
        public init() {}

        public init(progress: ProgressType) {
            self.progress = progress
        }

        public enum ProgressType: String, ExpressibleByArgument {
            case auto
            case none
            case ansi
            case plain
            case color
        }

        @Option(name: .long, help: ArgumentHelp("Progress type (format: auto|none|ansi|plain|color)", valueName: "type"))
        public var progress: ProgressType = .auto
    }

    public struct ImageFetch: ParsableArguments {
        public init() {}

        public init(maxConcurrentDownloads: Int) {
            self.maxConcurrentDownloads = maxConcurrentDownloads
        }

        @Option(name: .long, help: "Maximum number of concurrent downloads")
        public var maxConcurrentDownloads: Int = 3
    }
}
