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

import AppKit
import SwiftUI
import UniformTypeIdentifiers

struct MessageEntryView: View
{
    @Binding var viewModel: ViewModel
    @Environment(\.selectedConversation) private var selectedConversation
    
    var body: some View {
        VStack(spacing: 0.0) {
            Separator()
            
            HStack {
                VStack {
                    if !viewModel.attachments.isEmpty {
                        AttachmentWell(attachments: $viewModel.attachments)
                            .frame(height: 150.0)
                        
                        Separator()
                    }
                    
                    TextField("iMessage", text: $viewModel.draftText, axis: .vertical)
                        .focusEffectDisabled(true)
                        .textFieldStyle(.plain)
                        .lineLimit(nil)
                        .scrollContentBackground(.hidden)
                        .fixedSize(horizontal: false, vertical: true)
                        .font(.body)
                        .scrollDisabled(true)
                        .disabled(selectedConversation == nil)
                        .id("messageEntry")
                }
                .padding(8.0)
                .background {
                    RoundedRectangle(cornerRadius: 8.0)
                        .stroke(SeparatorShapeStyle())
                        .fill(.background)
                }
                
                Button("Send") {
                    viewModel.sendDraft(to: selectedConversation)
                }
                .disabled(viewModel.draftText.isEmpty && viewModel.uploadedAttachmentGUIDs.isEmpty)
                .keyboardShortcut(.defaultAction)
            }
            .padding(10.0)
        }
        .onChange(of: selectedConversation) { oldValue, newValue in
            if oldValue?.id != newValue?.id {
                viewModel.draftText = ""
            }
        }
    }
    
    // MARK: - Types
    
    @Observable
    class ViewModel
    {
        var draftText: String = ""
        var attachments: [OutgoingAttachment] = []
        var isDropTargeted: Bool = false
        
        var uploadedAttachmentGUIDs: Set<String> {
            attachments.reduce(Set<String>()) { partialResult, attachment in
                switch attachment.state {
                    case .uploaded(let guid): partialResult.union([ guid ])
                    default: partialResult
                }
            }
        }
        
        private let client = XPCClient()
        private var uploadGuidToAttachmentId: [String: String] = [:]
        private var signalTask: Task<Void, Never>? = nil

        init() {
            signalTask = Task { [weak self] in
                guard let self else { return }
                for await signal in client.eventStream() {
                    switch signal {
                    case .attachmentUploaded(let uploadGuid, let attachmentGuid):
                        // Mark local attachment as uploaded when the daemon confirms
                        await MainActor.run {
                            if let localId = self.uploadGuidToAttachmentId[uploadGuid],
                               let idx = self.attachments.firstIndex(where: { $0.id == localId }) {
                                self.attachments[idx].state = .uploaded(attachmentGuid)
                                self.uploadGuidToAttachmentId.removeValue(forKey: uploadGuid)
                            }
                        }
                    default:
                        break
                    }
                }
            }
        }

        deinit { signalTask?.cancel() }
        
        func sendDraft(to convo: Display.Conversation?) {
            guard let convo else { return }
            guard !(draftText.isEmpty && uploadedAttachmentGUIDs.isEmpty) else { return }
            
            let messageText = self.draftText
                .trimmingCharacters(in: .whitespacesAndNewlines)
            
            let transferGuids = self.uploadedAttachmentGUIDs
            
            self.draftText = ""
            self.attachments = []
            
            Task {
                do {
                    try await client.sendMessage(
                        conversationId: convo.id,
                        message: messageText,
                        transferGuids: transferGuids
                    )
                } catch {
                    print("Sending error: \(error)")
                }
            }
        }

        // MARK: - Attachments

        func handleDroppedProviders(_ providers: [NSItemProvider]) {
            let imageId = UTType.image.identifier
            let fileURLId = UTType.fileURL.identifier

            for provider in providers {
                if provider.hasItemConformingToTypeIdentifier(fileURLId) {
                    provider.loadItem(forTypeIdentifier: fileURLId, options: nil) { item, error in
                        if let error { print("Drop load fileURL error: \(error)"); return }
                        if let url = item as? URL {
                            self.stageAndAppend(url: url)
                        } else if let data = item as? Data,
                                  let s = String(data: data, encoding: .utf8),
                                  let url = URL(string: s.trimmingCharacters(in: .whitespacesAndNewlines)) {
                            self.stageAndAppend(url: url)
                        }
                    }
                    continue
                }

                if provider.hasItemConformingToTypeIdentifier(imageId) {
                    provider.loadFileRepresentation(forTypeIdentifier: imageId) { url, error in
                        if let error { print("Drop load image error: \(error)"); return }
                        guard let url else { return }
                        self.stageAndAppend(url: url)
                    }
                    continue
                }
            }
        }

        private func stageAndAppend(url: URL) {
            guard let att = AttachmentManager.shared.stageIfImage(url: url) else { return }
            Task { @MainActor in
                attachments.append(att)
                startUploadIfNeeded(for: att.id)
            }
        }

        private func startUploadIfNeeded(for attachmentId: String) {
            guard let idx = attachments.firstIndex(where: { $0.id == attachmentId }) else { return }
            
            let attachment = attachments[idx]
            switch attachment.state {
            case .staged, .failed:
                attachments[idx].state = .uploading(progress: 0.0)
                
                Task {
                    do {
                        let guid = try await client.uploadAttachment(path: attachment.stagedURL.path)
                        await MainActor.run {
                            self.uploadGuidToAttachmentId[guid] = attachmentId
                        }
                    } catch {
                        await MainActor.run {
                            if let i = self.attachments.firstIndex(where: { $0.id == attachmentId }) {
                                self.attachments[i].state = .failed
                            }
                        }
                    }
                }
            default:
                break
            }
        }
    }
    
    struct Separator: View
    {
        var body: some View {
            Rectangle()
                .fill(.separator)
                .frame(height: 1.0)
        }
    }

    struct AttachmentWell: View 
    {
        @Binding var attachments: [OutgoingAttachment]
        
        var body: some View {
            ScrollView(.horizontal) {
                LazyHStack(spacing: 8.0) {
                    ForEach(attachments) { attachment in
                        ZStack(alignment: .topTrailing) {
                            AttachmentThumbnail(attachment: attachment)
                                .clipShape(RoundedRectangle(cornerRadius: 8.0))
                                .overlay {
                                    RoundedRectangle(cornerRadius: 8.0)
                                        .stroke(.separator)
                                }

                            Button {
                                if let idx = attachments.firstIndex(where: { $0.id == attachment.id }) {
                                    attachments.remove(at: idx)
                                }
                            } label: {
                                Image(systemName: "xmark.circle.fill")
                                    .symbolRenderingMode(.hierarchical)
                                    .foregroundStyle(.secondary)
                            }
                            .buttonStyle(.plain)
                            .padding(6.0)
                        }
                    }
                    
                    Spacer()
                }
                .padding(8.0)
            }
        }
    }

    struct AttachmentThumbnail: View
    {
        let attachment: OutgoingAttachment
        
        var body: some View {
            ZStack {
                if let img = NSImage(contentsOf: attachment.stagedURL) {
                    Image(nsImage: img)
                        .resizable()
                        .scaledToFill()
                        .frame(width: 180.0, height: 120.0)
                        .clipped()
                        .background(.quaternary)
                } else {
                    ZStack {
                        Rectangle()
                            .fill(.quaternary)
                        Image(systemName: "photo")
                            .font(.title2)
                            .foregroundStyle(.secondary)
                    }
                    .frame(width: 180.0, height: 120.0)
                }

                switch attachment.state {
                case .uploading(let progress):
                    VStack {
                        Spacer()
                        ProgressView(value: progress)
                            .progressViewStyle(.linear)
                            .tint(.accentColor)
                            .padding(.horizontal, 8.0)
                            .padding(.bottom, 6.0)
                    }
                case .failed:
                    HStack {
                        Spacer()
                        Image(systemName: "exclamationmark.triangle.fill")
                            .foregroundStyle(.yellow)
                            .padding(6.0)
                    }
                default:
                    EmptyView()
                }
            }
        }
    }
}

#Preview {
    @Previewable @State var model = MessageEntryView.ViewModel()
    
    VStack {
        Spacer()
        MessageEntryView(viewModel: $model)
    }
}
