//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization 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 Foundation

/// The core error type for Containerization.
///
/// Most API surfaces for the core container/process/agent types will
/// return a ContainerizationError.
public struct ContainerizationError: Swift.Error, Sendable {
    /// A code describing the error encountered.
    public var code: Code
    /// A description of the error.
    public var message: String
    /// The original error which led to this error being thrown.
    public var cause: (any Error)?

    /// Creates a new error.
    ///
    /// - Parameters:
    ///   - code: The error code.
    ///   - message: A description of the error.
    ///   - cause: The original error which led to this error being thrown.
    public init(_ code: Code, message: String, cause: (any Error)? = nil) {
        self.code = code
        self.message = message
        self.cause = cause
    }

    /// Creates a new error.
    ///
    /// - Parameters:
    ///   - rawCode: The error code value as a String.
    ///   - message: A description of the error.
    ///   - cause: The original error which led to this error being thrown.
    public init(_ rawCode: String, message: String, cause: (any Error)? = nil) {
        self.code = Code(rawValue: rawCode)
        self.message = message
        self.cause = cause
    }

    /// Provides a unique hash of the error.
    public func hash(into hasher: inout Hasher) {
        hasher.combine(self.code)
        hasher.combine(self.message)
    }

    /// Equality operator for the error. Uses the code and message.
    public static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.code == rhs.code && lhs.message == rhs.message
    }

    /// Checks if the given error has the provided code.
    public func isCode(_ code: Code) -> Bool {
        self.code == code
    }
}

extension ContainerizationError: CustomStringConvertible {
    /// Description of the error.
    public var description: String {
        guard let cause = self.cause else {
            return "\(self.code): \"\(self.message)\""
        }
        return "\(self.code): \"\(self.message)\" (cause: \"\(cause)\")"
    }
}

extension ContainerizationError: LocalizedError {
    /// A localized message describing what error occurred.
    public var errorDescription: String? {
        guard let cause = self.cause else {
            return message
        }
        return "\(message) (cause: \"\(cause)\")"
    }
}

extension ContainerizationError {
    /// Codes for a `ContainerizationError`.
    public struct Code: Sendable, Hashable {
        private enum Value: Hashable, Sendable, CaseIterable {
            case unknown
            case invalidArgument
            case internalError
            case exists
            case notFound
            case cancelled
            case invalidState
            case empty
            case timeout
            case unsupported
            case interrupted
        }

        private var value: Value
        private init(_ value: Value) {
            self.value = value
        }

        init(rawValue: String) {
            let values = Value.allCases.reduce(into: [String: Value]()) {
                $0[String(describing: $1)] = $1
            }

            let match = values[rawValue]
            guard let match else {
                fatalError("invalid code value \(rawValue)")
            }
            self.value = match
        }

        public static var unknown: Self {
            Self(.unknown)
        }

        public static var invalidArgument: Self {
            Self(.invalidArgument)
        }

        public static var internalError: Self {
            Self(.internalError)
        }

        public static var exists: Self {
            Self(.exists)
        }

        public static var notFound: Self {
            Self(.notFound)
        }

        public static var cancelled: Self {
            Self(.cancelled)
        }

        public static var invalidState: Self {
            Self(.invalidState)
        }

        public static var empty: Self {
            Self(.empty)
        }

        public static var timeout: Self {
            Self(.timeout)
        }

        public static var unsupported: Self {
            Self(.unsupported)
        }

        public static var interrupted: Self {
            Self(.interrupted)
        }
    }
}

extension ContainerizationError.Code: CustomStringConvertible {
    public var description: String {
        String(describing: self.value)
    }
}
