import Foundation

public enum ClawdbotCanvasA2UIJSONL: Sendable {
    public struct ParsedItem: Sendable {
        public var lineNumber: Int
        public var message: AnyCodable

        public init(lineNumber: Int, message: AnyCodable) {
            self.lineNumber = lineNumber
            self.message = message
        }
    }

    public static func parse(_ text: String) throws -> [ParsedItem] {
        var out: [ParsedItem] = []
        var lineNumber = 0
        for rawLine in text.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) {
            lineNumber += 1
            let line = String(rawLine).trimmingCharacters(in: .whitespacesAndNewlines)
            if line.isEmpty { continue }
            let data = Data(line.utf8)

            let decoded = try JSONDecoder().decode(AnyCodable.self, from: data)
            out.append(ParsedItem(lineNumber: lineNumber, message: decoded))
        }
        return out
    }

    public static func validateV0_8(_ items: [ParsedItem]) throws {
        let allowed = Set([
            "beginRendering",
            "surfaceUpdate",
            "dataModelUpdate",
            "deleteSurface",
        ])
        for item in items {
            guard let dict = item.message.value as? [String: AnyCodable] else {
                throw NSError(domain: "A2UI", code: 1, userInfo: [
                    NSLocalizedDescriptionKey: "A2UI JSONL line \(item.lineNumber): expected a JSON object",
                ])
            }

            if dict.keys.contains("createSurface") {
                throw NSError(domain: "A2UI", code: 2, userInfo: [
                    NSLocalizedDescriptionKey: """
                    A2UI JSONL line \(item.lineNumber): looks like A2UI v0.9 (`createSurface`).
                    Canvas currently supports A2UI v0.8 server→client messages
                    (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`, `deleteSurface`).
                    """,
                ])
            }

            let matched = dict.keys.filter { allowed.contains($0) }
            if matched.count != 1 {
                let found = dict.keys.sorted().joined(separator: ", ")
                throw NSError(domain: "A2UI", code: 3, userInfo: [
                    NSLocalizedDescriptionKey: """
                    A2UI JSONL line \(item.lineNumber): expected exactly one of \(allowed.sorted()
                        .joined(separator: ", ")); found: \(found)
                    """,
                ])
            }
        }
    }

    public static func decodeMessagesFromJSONL(_ text: String) throws -> [AnyCodable] {
        let items = try self.parse(text)
        try self.validateV0_8(items)
        return items.map(\.message)
    }

    public static func encodeMessagesJSONArray(_ messages: [AnyCodable]) throws -> String {
        let data = try JSONEncoder().encode(messages)
        guard let json = String(data: data, encoding: .utf8) else {
            throw NSError(domain: "A2UI", code: 10, userInfo: [
                NSLocalizedDescriptionKey: "Failed to encode messages payload as UTF-8",
            ])
        }
        return json
    }
}
