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

import SwiftUI

struct TranscriptView: View
{
    @Binding var model: ViewModel
    
    @Environment(\.xpcClient) private var xpcClient
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 6.0) {
                ForEach($model.displayItems.reversed()) { item in
                    displayItemView(item.wrappedValue)
                        .id(item.id)
                        .scaleEffect(CGSize(width: 1.0, height: -1.0))
                        .transition(
                            .push(from: .top)
                            .combined(with: .opacity)
                        )
                }
            }
            .padding()
        }
        .scaleEffect(CGSize(width: 1.0, height: -1.0))
        .id(model.displayedConversation?.id)
        .task { await watchForMessageListChanges() }
    }
    
    private func watchForMessageListChanges() async {
        for await event in xpcClient.eventStream() {
            switch event {
                case .attachmentDownloaded(let attachmentId):
                    model.attachmentDownloaded(id: attachmentId)
                case .messagesUpdated(let conversationId):
                    if let displayedConversation = model.displayedConversation,
                        conversationId == displayedConversation.id
                    {
                        model.setNeedsReload(animated: true)
                    }
                case .updateStreamReconnected:
                    await model.triggerSync()
                default:
                    break
            }
        }
    }
    
    @ViewBuilder
    private func displayItemView(_ item: DisplayItem) -> some View {
        switch item {
        case .message(let message):
            TextBubbleItemView(text: message.text, sender: message.sender, date: message.date)
        case .date(let date):
            DateItemView(date: date)
        case .senderAttribition(let message):
            SenderAttributionView(sender: message.sender)
        case .spacer(let length, _):
            Spacer(minLength: length)
        case .attachment(let attachment):
            if attachment.isPreviewDownloaded {
                ImageItemView(
                    sender: attachment.sender,
                    date: attachment.dateSent,
                    attachment: attachment
                )
            } else {
                PlaceholderImageItemView(
                    sender: attachment.sender,
                    date: attachment.dateSent,
                    size: attachment.size
                )
            }
        }
    }
    
    // MARK: - Types
    
    @Observable
    class ViewModel
    {
        var displayItems: [DisplayItem] = []
        var displayedConversation: Display.Conversation? = nil
        
        internal var needsReload: NeedsReload = .no
        internal var messages: [Display.Message]
        internal let client = XPCClient()

        init(messages: [Display.Message] = []) {
            self.messages = messages
            observeDisplayedConversation()
            rebuildDisplayItems()
        }
        
        func setNeedsReload(animated: Bool) {
            guard case .no = needsReload else {
                return
            }

            needsReload = .yes(animated)
            Task { @MainActor [weak self] in
                guard let self else { return }
                await reloadMessages()
            }
        }
        
        func attachmentDownloaded(id: String) {
            // TODO: should be smarter here
            setNeedsReload(animated: false)
        }
        
        private func observeDisplayedConversation() {
            withObservationTracking {
                _ = displayedConversation
            } onChange: {
                Task { @MainActor [weak self] in
                    guard let self else { return }
                    
                    await markAsRead()
                    await triggerSync()
                    
                    setNeedsReload(animated: false)
                    observeDisplayedConversation()
                }
            }
        }
        
        func markAsRead() async {
            guard let displayedConversation else { return }
            
            do {
                try await client.markConversationAsRead(conversationId: displayedConversation.id)
            } catch {
                print("Error triggering sync: \(error)")
            }
        }
        
        func triggerSync() async {
            guard let displayedConversation else { return }
            
            do {
                try await client.syncConversation(conversationId: displayedConversation.id)
            } catch {
                print("Error triggering sync: \(error)")
            }
        }
        
        private func reloadMessages() async {
            guard case .yes(let animated) = needsReload else { return }
            needsReload = .no
            
            guard let displayedConversation else { return }
            
            do {
                let clientMessages = try await client.getMessages(conversationId: displayedConversation.id)
                    .map { Display.Message(from: $0) }

                let newIds = Set(clientMessages.map(\.id))
                    .subtracting(self.messages.map(\.id))

                // Only animate for incoming messages.
                let shouldAnimate = (newIds.count == 1)

                self.messages = clientMessages
                self.rebuildDisplayItems(animated: animated && shouldAnimate)
            } catch {
                print("Message fetch error: \(error)")
            }
        }
        
        // MARK: - Types
        
        enum NeedsReload
        {
            case no
            case yes(Bool) // animated
        }
    }
}

#Preview {
    @Previewable @State var model = TranscriptView.ViewModel(messages: [
        .init(sender: .me,                 text: "Hello, how are you?"),
        .init(sender: .counterpart("Bob"), text: "I am doing fine!")
    ])
    
    TranscriptView(model: $model)
}
