import Foundation
import Combine

/// Manages conversations: message appending, prefetching, NPC typing,
/// conversation errors, and navigation to chat views.
class ConversationService: ObservableObject {
    @Published var awaitingConversationResponse: String = ""
    @Published var personReceived: Bool = false
    @Published var pendingOpenConversation: String? = nil

    private var pendingConversationRequests: Set<String> = []

    weak var facade: WebSocketService?

    func sendMessage(_ message: [String: Any]) {
        facade?.sendMessage(message: message)
    }

    // MARK: - Actions

    func appendMessage(_ message: ConversationMessage, to conversation: ConversationObj) {
        guard let characterId = conversation.character else { return }
        guard let facade = facade else { return }
        for (index, existingConversation) in facade.player.activeConversations.enumerated() where existingConversation.character == characterId {
            facade.player.activeConversations[index].conversation.append(message)
        }
    }

    func prefetchConversation(forCharacter characterId: String) {
        guard let facade = facade else { return }
        guard !pendingConversationRequests.contains(characterId) else { return }

        let hasLoadedConversation = facade.player.activeConversations.contains {
            $0.character == characterId && !$0.conversation.isEmpty
        }
        guard !hasLoadedConversation else { return }

        pendingConversationRequests.insert(characterId)

        let convEvent: [String: Any] = [
            "conversationEvent": "init",
            "characterID": characterId,
            "cType": "chat"
        ]
        let message: [String: Any] = [
            "type": "conversation",
            "message": convEvent
        ]
        sendMessage(message)
    }

    func clearPendingConversationRequest(forCharacter characterId: String) {
        pendingConversationRequests.remove(characterId)
    }

    func openConversation(characterId: String) {
        self.pendingOpenConversation = characterId
    }

    // MARK: - Message Handling

    func handleMessage(type: String, data: [String: Any]) -> Bool {
        switch type {
        case "conversationEvent":
            handleConversationEvent(data)
            return true
        case "conversationError":
            handleConversationError(data)
            return true
        case "npcTyping":
            handleNPCTyping(data)
            return true
        default:
            return false
        }
    }

    // MARK: - Private Handlers

    private func handleConversationEvent(_ data: [String: Any]) {
        guard let facade = facade else { return }

        do {
            self.awaitingConversationResponse = ""
            let jsonData = try JSONSerialization.data(withJSONObject: data)

            let decoder = JSONDecoder()
            var newConversation = try decoder.decode(ConversationObj.self, from: jsonData)

            // Memory optimization: Limit messages to 100 per conversation
            if newConversation.conversation.count > 100 {
                newConversation.conversation = Array(newConversation.conversation.suffix(100))
            }

            // Clear pending request status
            if let characterId = newConversation.character {
                self.clearPendingConversationRequest(forCharacter: characterId)
            }

            // Simple update: server data replaces existing
            if let characterId = newConversation.character,
               let index = facade.player.activeConversations.firstIndex(where: { $0.character == characterId }) {
                facade.player.activeConversations[index] = newConversation
            } else {
                facade.player.activeConversations.append(newConversation)
            }

            // Show in-app toast for unread NPC messages
            // Skip toast when an event modal is on screen (ZStack overlays can't be dismissed)
            if newConversation.unread == true,
               facade.currentQuestion == nil,
               facade.currentMessageEvent == nil,
               let characterId = newConversation.character,
               let npc = facade.player.r.first(where: { $0.id == characterId }) {
                let lastMessage = newConversation.conversation.last
                let preview = lastMessage?.message ?? "sent you a message"
                let truncated = preview.count > 80 ? String(preview.prefix(80)) + "..." : preview
                let npcName = npc.firstname ?? "Someone"

                ToastManager.shared.showMessage(
                    from: npcName,
                    preview: truncated,
                    duration: 4.0,
                    onTap: { [weak self] in
                        self?.openConversation(characterId: characterId)
                    }
                )
            }
        } catch let DecodingError.dataCorrupted(context) {
            #if DEBUG
            print("Data corrupted: \(context)")
            #endif
        } catch let DecodingError.keyNotFound(key, context) {
            #if DEBUG
            print("Key '\(key)' not found: \(context.debugDescription)")
            print("codingPath: \(context.codingPath)")
            #endif
        } catch let DecodingError.valueNotFound(value, context) {
            #if DEBUG
            print("Value '\(value)' not found: \(context.debugDescription)")
            print("codingPath: \(context.codingPath)")
            #endif
        } catch let DecodingError.typeMismatch(type, context) {
            #if DEBUG
            print("Type '\(type)' mismatch: \(context.debugDescription)")
            print("codingPath: \(context.codingPath)")
            #endif
        } catch {
            #if DEBUG
            print("Failed to decode conversation data: \(error)")
            #endif
        }
    }

    private func handleConversationError(_ data: [String: Any]) {
        let tempId = data["tempId"] as? String
        let reason = data["message"] as? String ?? "Message failed"
        NotificationCenter.default.post(
            name: NSNotification.Name("ConversationMessageFailed"),
            object: nil,
            userInfo: ["tempId": tempId as Any, "reason": reason]
        )
    }

    private func handleNPCTyping(_ data: [String: Any]) {
        let characterId = data["characterID"] as? String ?? ""
        NotificationCenter.default.post(
            name: NSNotification.Name("NPCTypingStarted"),
            object: nil,
            userInfo: ["characterID": characterId]
        )
    }
}
