//
//  Models.swift
//  kordophone2
//
//  Created by James Magahern on 8/24/25.
//

import Foundation
import XPC

enum Display
{
    struct Conversation: Identifiable, Hashable, Codable
    {
        let id: String
        let name: String?
        let participants: [String]
        let messagePreview: String
        let unreadCount: Int
        
        var displayName: String {
            if let name, name.count > 0 { return name }
            else { return participants.joined(separator: ", ") }
        }
        
        var isGroupChat: Bool {
            participants.count > 1
        }

        var isUnread: Bool {
            unreadCount > 0
        }

        init(from c: Serialized.Conversation) {
            self.id = c.guid
            self.name = c.displayName
            self.participants = c.participants
            self.messagePreview = c.lastMessagePreview ?? ""
            self.unreadCount = c.unreadCount
        }
        
        init(id: String = UUID().uuidString, name: String? = nil, participants: [String], messagePreview: String) {
            self.id = id
            self.name = name
            self.participants = participants
            self.messagePreview = messagePreview
            self.unreadCount = 0
        }
    }
    
    struct Message: Identifiable, Hashable
    {
        let id: String
        let sender: Sender
        let text: String
        let date: Date
        let attachments: [ImageAttachment]
        
        var isFromMe: Bool { sender.isMe }

        init(from m: Serialized.Message) {
            self.id = m.guid
            self.text = m.text
            self.date = m.date
            
            let sender: Sender = if m.sender == "(Me)" {
                .me
            } else {
                .counterpart(m.sender)
            }
            
            self.attachments = m.attachments.map { attachment in
                ImageAttachment(from: attachment, dateSent: m.date, sender: sender)
            }
            
            self.sender = sender
        }
        
        init(id: String = UUID().uuidString, sender: Sender = .me, date: Date = .now, text: String) {
            self.id = id
            self.sender = sender
            self.text = text
            self.date = date
            self.attachments = []
        }

        static func == (lhs: Message, rhs: Message) -> Bool {
            lhs.id == rhs.id
        }
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(id)
        }
    }
    
    struct ImageAttachment: Identifiable
    {
        let id: String
        let sender: Sender
        let dateSent: Date
        let data: Serialized.Attachment
        
        var size: CGSize? {
            if let attr = data.metadata?.attributionInfo, let width = attr.width, let height = attr.height {
                return CGSize(width: width, height: height)
            }
            
            return nil 
        }
        
        var isPreviewDownloaded: Bool {
            data.isPreviewDownloaded
        }
        
        var previewPath: String {
            data.previewPath
        }

        var isFullsizeDownloaded: Bool {
            data.isDownloaded
        }

        var fullsizePath: String {
            data.path
        }

        init(from serialized: Serialized.Attachment, dateSent: Date, sender: Sender) {
            self.id = serialized.guid
            self.sender = sender
            self.data = serialized
            self.dateSent = dateSent
        }
    }
    
    enum Sender: Identifiable, Equatable
    {
        case me
        case counterpart(String)
        
        var id: String { displayName }
        
        var isMe: Bool {
            if case .me = self { true } else { false }
        }
        
        var displayName: String {
            switch self {
            case .me: 
                "Me"
            case .counterpart(let string):
                string
            }
        }
        
        static func ==(lhs: Sender, rhs: Sender) -> Bool {
            return lhs.displayName == rhs.displayName
        }
    }
}

enum Serialized
{
    struct Conversation: Decodable
    {
        let guid: String
        let displayName: String?
        let participants: [String]
        let lastMessagePreview: String?
        let unreadCount: Int
        let date: Date

        init?(xpc dict: xpc_object_t)
        {
            guard let d = XPCDictionary(dict), let g: String = d["guid"] else { return nil }

            let dn: String? = d["display_name"]
            let lmp: String? = d["last_message_preview"]
            let names: [String] = d["participants"] ?? []
            let unread: Int = d["unread_count"] ?? 0
            let dt: Date = d["date"] ?? Date(timeIntervalSince1970: 0)

            self.guid = g
            self.displayName = dn
            self.participants = names
            self.lastMessagePreview = lmp
            self.unreadCount = unread
            self.date = dt
        }
    }

    struct Message: Decodable
    {
        let guid: String
        let sender: String
        let text: String
        let date: Date
        let attachments: [Attachment]

        init?(xpc dict: xpc_object_t)
        {
            guard let d = XPCDictionary(dict), let g: String = d["id"] else { return nil }

            let s: String = d["sender"] ?? ""
            let t: String = d["text"] ?? ""
            let dd: Date = d["date"] ?? Date(timeIntervalSince1970: 0)
            let atts: [Attachment] = d["attachments"] ?? []

            self.guid = g
            self.sender = s
            self.text = t
            self.date = dd
            self.attachments = atts
        }
    }

    struct Attachment: Decodable
    {
        let guid: String
        let path: String
        let previewPath: String
        let isDownloaded: Bool
        let isPreviewDownloaded: Bool
        let metadata: Metadata?

        struct Metadata: Decodable
        {
            let attributionInfo: AttributionInfo?
        }

        struct AttributionInfo: Decodable
        {
            let width: Int?
            let height: Int?
        }
    }

    struct Settings: Decodable
    {
        let serverUrl: String
        let username: String
    }
}

extension Serialized.Settings: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Serialized.Settings? {
        guard let d = XPCDictionary(value) else { return nil }
        
        let su: String = d["server_url"] ?? ""
        let un: String = d["username"] ?? ""

        return Serialized.Settings(
            serverUrl: su,
            username: un
        )
    }
    
}

extension Serialized.Attachment: XPCConvertible
{
    static func fromXPC(_ value: xpc_object_t) -> Serialized.Attachment? {
        guard let d = XPCDictionary(value), let guid: String = d["guid"] else { return nil }

        let path: String = d["path"] ?? ""
        let previewPath: String = d["preview_path"] ?? ""

        // Booleans are encoded as strings in XPC
        let downloadedStr: String = d["downloaded"] ?? "false"
        let previewDownloadedStr: String = d["preview_downloaded"] ?? "false"
        let isDownloaded = downloadedStr == "true"
        let isPreviewDownloaded = previewDownloadedStr == "true"

        var metadata: Serialized.Attachment.Metadata? = nil
        if let metadataObj = d.object("metadata"), let md = XPCDictionary(metadataObj) {
            var attribution: Serialized.Attachment.AttributionInfo? = nil
            if let attrObj = md.object("attribution_info"), let ad = XPCDictionary(attrObj) {
                let width: Int? = ad["width"]
                let height: Int? = ad["height"]
                attribution = Serialized.Attachment.AttributionInfo(width: width, height: height)
            }
            metadata = Serialized.Attachment.Metadata(attributionInfo: attribution)
        }

        return Serialized.Attachment(
            guid: guid,
            path: path,
            previewPath: previewPath,
            isDownloaded: isDownloaded,
            isPreviewDownloaded: isPreviewDownloaded,
            metadata: metadata
        )
    }
}
