import Foundation

/// Server → client WS frames (frozen contract, plan §3.1; mirrors
/// `src/types.ts:109-120` `ServerMessage`). Decoded ONLY via
/// `MessageCodec.decodeServer` — the server is an untrusted input source and
/// malformed frames become `nil`, never a crash.
public enum ServerMessage: Sendable, Equatable {
    /// Attach confirmation. ALWAYS adopt the server-issued id — attaching with
    /// an unknown UUID yields a fresh session id (src/session/manager.ts:108-174).
    case attached(sessionId: UUID)
    /// Opaque ANSI/UTF-8 bytes; ring-buffer replay and live stream share this shape.
    case output(data: String)
    /// Shell exit. `code == WireConstants.spawnFailedExitCode` (-1) means the
    /// spawn never succeeded (M4) and `reason` is required server-side.
    case exit(code: Int, reason: String?)
    /// Claude Code activity derived from hooks (H2/H3/B4). `pending` = a tool
    /// approval is held server-side; `gate` says which kind. Absent `pending`
    /// decodes as `false`; an unrecognized `gate` value decodes as `nil`
    /// (tolerated as absent so the pending signal is never lost).
    case status(ClaudeStatus, detail: String?, pending: Bool, gate: GateKind?)
    /// Latest statusLine telemetry broadcast (B2).
    case telemetry(StatusTelemetry)
}

/// Mirrors `src/types.ts:97` `ClaudeStatus`. `unknown` = no hook signal yet;
/// `stuck` (A5) = output silent past STUCK_TTL while not idle/exited.
public enum ClaudeStatus: String, Sendable {
    case working, waiting, idle, unknown, stuck
}

/// Mirrors `src/types.ts:101` `PermissionGate`. `plan` = ExitPlanMode three-way
/// gate; `tool` = ordinary tool gate.
public enum GateKind: String, Sendable {
    case tool, plan
}

/// Mirrors `src/types.ts:406-416` `StatusTelemetry`: every metric optional,
/// `at` (server receive time, ms since epoch) required. Decoding is tolerant —
/// a wrong-typed OPTIONAL field is treated as absent (mirrors the server's own
/// tolerant statusLine parsing); a missing/wrong-typed `at` fails the decode.
public struct StatusTelemetry: Sendable, Equatable, Decodable {
    public let contextUsedPct: Double?
    public let costUsd: Double?
    public let linesAdded: Int?
    public let linesRemoved: Int?
    public let model: String?
    public let effort: String?
    public let pr: PrInfo?
    public let rate: RateInfo?
    /// Server receive timestamp (ms). REQUIRED — frames without it are dropped.
    public let at: Int

    public init(
        contextUsedPct: Double? = nil,
        costUsd: Double? = nil,
        linesAdded: Int? = nil,
        linesRemoved: Int? = nil,
        model: String? = nil,
        effort: String? = nil,
        pr: PrInfo? = nil,
        rate: RateInfo? = nil,
        at: Int
    ) {
        self.contextUsedPct = contextUsedPct
        self.costUsd = costUsd
        self.linesAdded = linesAdded
        self.linesRemoved = linesRemoved
        self.model = model
        self.effort = effort
        self.pr = pr
        self.rate = rate
        self.at = at
    }

    private enum CodingKeys: String, CodingKey {
        case contextUsedPct, costUsd, linesAdded, linesRemoved
        case model, effort, pr, rate, at
    }

    public init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // `at` is the only required field; its absence invalidates the frame.
        at = try container.decode(Int.self, forKey: .at)
        // Optional fields: `try?` treats wrong-typed values as absent (tolerant).
        contextUsedPct = try? container.decode(Double.self, forKey: .contextUsedPct)
        costUsd = try? container.decode(Double.self, forKey: .costUsd)
        linesAdded = try? container.decode(Int.self, forKey: .linesAdded)
        linesRemoved = try? container.decode(Int.self, forKey: .linesRemoved)
        model = try? container.decode(String.self, forKey: .model)
        effort = try? container.decode(String.self, forKey: .effort)
        pr = try? container.decode(PrInfo.self, forKey: .pr)
        rate = try? container.decode(RateInfo.self, forKey: .rate)
    }
}

/// Mirrors `src/types.ts:413` `StatusTelemetry.pr`.
public struct PrInfo: Sendable, Equatable, Decodable {
    public let number: Int
    public let url: String
    public let reviewState: String?

    public init(number: Int, url: String, reviewState: String? = nil) {
        self.number = number
        self.url = url
        self.reviewState = reviewState
    }
}

/// Mirrors `src/types.ts:414` `StatusTelemetry.rate`.
public struct RateInfo: Sendable, Equatable, Decodable {
    public let fiveHourPct: Double?
    public let sevenDayPct: Double?

    public init(fiveHourPct: Double? = nil, sevenDayPct: Double? = nil) {
        self.fiveHourPct = fiveHourPct
        self.sevenDayPct = sevenDayPct
    }
}
