import Foundation

/// Test-side mini-port of the server's `parseClientMessage` (src/protocol.ts:45-176)
/// plus `parseApproveMode` (src/server.ts:94-102). Used by the roundtrip property
/// test and the cross-implementation vector tests: `MessageCodec.encode` output is
/// fed through THIS parser, which itself is pinned against the vectors transcribed
/// from test/protocol.test.ts — so the Swift encoder and the TS server parser
/// cannot drift silently (the live tripwire is T-iOS-16's real-server CI).
enum ServerViewMessage: Equatable {
    case attach(sessionId: String?, cwd: String?)
    case input(data: String)
    case resize(cols: Int, rows: Int)
    case approve
    case reject
}

enum ServerViewParser {
    /// Mirror of src/protocol.ts SESSION_ID_RE (UUID v4, /i).
    private static let sessionIdPattern =
        "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"

    /// Mirror of src/server.ts:76 PERMISSION_MODES.
    static let permissionModes: Set<String> = ["default", "acceptEdits", "plan", "auto"]

    /// Mirror of src/protocol.ts:27 ALLOWED_TYPES.
    private static let allowedTypes: Set<String> = ["attach", "input", "resize", "approve", "reject"]

    // MARK: - parseClientMessage mirror (never throws; invalid → nil)

    static func parse(_ raw: String) -> ServerViewMessage? {
        guard let data = raw.data(using: .utf8),
              let parsed = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]),
              let obj = parsed as? [String: Any],
              let type = obj["type"] as? String,
              allowedTypes.contains(type)
        else { return nil }

        switch type {
        case "resize": return parseResize(obj)
        case "input": return parseInput(obj)
        case "approve": return .approve
        case "reject": return .reject
        default: return parseAttach(obj)
        }
    }

    /// Mirror of src/server.ts:94-102 — reads `mode` from the RAW frame's top level.
    static func topLevelMode(_ raw: String) -> String? {
        guard let data = raw.data(using: .utf8),
              let obj = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
              let mode = obj["mode"] as? String,
              permissionModes.contains(mode)
        else { return nil }
        return mode
    }

    // MARK: - Per-type validators (src/protocol.ts:90-176)

    private static func parseResize(_ obj: [String: Any]) -> ServerViewMessage? {
        guard let cols = integerDimension(obj["cols"]),
              let rows = integerDimension(obj["rows"])
        else { return nil }
        return .resize(cols: cols, rows: rows)
    }

    /// Mirror of isValidDimension: number, integer, 1...1000 (src/protocol.ts:113-115).
    private static func integerDimension(_ value: Any?) -> Int? {
        guard let number = value as? NSNumber, !isJSONBoolean(number) else { return nil }
        let doubleValue = number.doubleValue
        guard doubleValue == doubleValue.rounded(.towardZero),
              doubleValue >= 1, doubleValue <= 1000
        else { return nil }
        return number.intValue
    }

    private static func isJSONBoolean(_ number: NSNumber) -> Bool {
        CFGetTypeID(number) == CFBooleanGetTypeID()
    }

    private static func parseInput(_ obj: [String: Any]) -> ServerViewMessage? {
        guard let data = obj["data"] as? String else { return nil }
        return .input(data: data)
    }

    private static func parseAttach(_ obj: [String: Any]) -> ServerViewMessage? {
        // sessionId key must be explicitly present (src/protocol.ts:132-134).
        guard obj.index(forKey: "sessionId") != nil else { return nil }

        var cwd: String?
        if let rawCwd = obj["cwd"] {
            guard let cwdString = rawCwd as? String, cwdString.hasPrefix("/") else { return nil }
            cwd = cwdString
        }

        if obj["sessionId"] is NSNull {
            return .attach(sessionId: nil, cwd: cwd)
        }
        guard let sessionId = obj["sessionId"] as? String, isServerValidSessionId(sessionId) else {
            return nil
        }
        return .attach(sessionId: sessionId, cwd: cwd)
    }

    static func isServerValidSessionId(_ candidate: String) -> Bool {
        candidate.range(
            of: sessionIdPattern,
            options: [.regularExpression, .caseInsensitive]
        ) != nil
    }
}
