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

import SwiftUI

struct BubbleView<Content: View>: View
{
    let date: Date
    let sender: Display.Sender
    let content: () -> Content

    private var isFromMe: Bool { sender.isMe }
    
    private let tooltipDateFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEEE, MMMM dd, HH:mm"
        
        return f
    }()


    init(sender: Display.Sender, date: Date, @ViewBuilder content: @escaping () -> Content) {
        self.sender = sender
        self.content = content
        self.date = date
    }
    
    var body: some View {
        VStack(alignment: isFromMe ? .trailing : .leading) {
            HStack(alignment: .bottom) {
                if isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }

                content()
                    .mask {
                        UnevenRoundedRectangle(cornerRadii: RectangleCornerRadii(
                            topLeading: isFromMe ? .dominantCornerRadius : .minorCornerRadius,
                            bottomLeading: .dominantCornerRadius,
                            bottomTrailing: .dominantCornerRadius,
                            topTrailing: isFromMe ? .minorCornerRadius : .dominantCornerRadius
                        ))
                    }

                if !isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }
            }
        }
        .help(tooltipDateFormatter.string(from: date))
    }
}

struct TextBubbleItemView: View
{
    let text: String
    let sender: Display.Sender
    let date: Date
    
    private var isFromMe: Bool { sender.isMe }

    var body: some View {
        let bubbleColor: Color = isFromMe ? .blue : Color(NSColor(name: "grayish", dynamicProvider: { appearance in
            appearance.name == .darkAqua ? .darkGray : NSColor(white: 0.78, alpha: 1.0)
        }))
        let textColor: Color = isFromMe ? .white : .primary

        BubbleView(sender: sender, date: date) {
            HStack {
                Text(text.linkifiedAttributedString())
                    .foregroundStyle(textColor)
                    .multilineTextAlignment(.leading)
            }
            .fixedSize(horizontal: false, vertical: true)
            .padding(.horizontal, 16.0)
            .padding(.vertical, 10.0)
            .background(bubbleColor)
            .textSelection(.enabled)
        }
    }
}

struct ImageItemView: View
{
    let sender: Display.Sender
    let date: Date
    let attachment: Display.ImageAttachment
    
    @State private var img: NSImage?
    @Environment(\.xpcClient) var xpcClient
    
    @State private var containerWidth: CGFloat? = nil
    @State private var isDownloadingFullAttachment: Bool = false

    private var aspectRatio: CGFloat {
        attachment.size?.aspectRatio ?? 1.0
    }
        
    private var preferredWidth: CGFloat {
        preferredBubbleWidth(forAttachmentSize: attachment.size, containerWidth: containerWidth, maxWidth: .imageMaxWidth)
    }
        
    var body: some View {
        BubbleView(sender: sender, date: date) {
            let maxWidth = CGFloat.minimum(.imageMaxWidth, containerWidth ?? .imageMaxWidth)

            Group {
                if let img {
                    Image(nsImage: img)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: maxWidth)
                } else {
                    Rectangle()
                        .fill(.gray.opacity(0.4))
                        .frame(width: preferredWidth, height: preferredWidth / aspectRatio)
                        .frame(maxWidth: maxWidth)
                }
            }

            // Download indicator
            .overlay {
                if isDownloadingFullAttachment {
                    ZStack {
                        Rectangle()
                            .fill(.black.opacity(0.2))

                        ProgressView()
                            .progressViewStyle(.circular)
                    }
                }
            }
        }
        .onTapGesture(count: 2) {
            openAttachment()
        }
        .onGeometryChange(for: CGFloat.self,
                          of: { $0.size.width },
                          action: { containerWidth = $0 })
        .task {
            do {
                let handle = try await xpcClient.openAttachmentFileHandle(
                    attachmentId: attachment.id,
                    preview: true
                )
                
                try? handle.seek(toOffset: 0)
                if let data = try? handle.readToEnd(),
                   let ns = NSImage(data: data) {
                    img = ns
                }
                
                try handle.close()
            } catch {
                print("Attachment file handle acquisition error: \(error)")
            }
        }
    }

    private func openAttachment() {
        Task {
            var path = attachment.fullsizePath
            if !attachment.isFullsizeDownloaded {
                isDownloadingFullAttachment = true
                try await xpcClient.downloadAttachment(attachmentId: attachment.id, preview: false, awaitCompletion: true)

                // Need to re-fetch this -- the extension may have changed.
                let info = try await xpcClient.getAttachmentInfo(attachmentId: attachment.id)
                path = info.path

                isDownloadingFullAttachment = false
            }

            PreviewPanel.shared.show(url: URL(filePath: path))
        }
    }
}

struct PlaceholderImageItemView: View
{
    let sender: Display.Sender
    let date: Date
    let size: CGSize?
    
    @State private var containerWidth: CGFloat? = nil
    
    private var aspectRatio: CGFloat {
        size?.aspectRatio ?? 1.0
    }
        
    private var preferredWidth: CGFloat {
        preferredBubbleWidth(forAttachmentSize: size, containerWidth: containerWidth, maxWidth: .imageMaxWidth)
    }

    init(sender: Display.Sender, date: Date, size: CGSize?) {
        self.sender = sender
        self.date = date
        self.size = size
    }
    
    var body: some View {
        BubbleView(sender: sender, date: date) {
            ZStack {
                Rectangle()
                    .fill(.gray.opacity(0.4))
                    .frame(width: preferredWidth, height: preferredWidth / aspectRatio)
                    .frame(maxWidth: .imageMaxWidth)
                
                ProgressView()
            }
        }
        .onGeometryChange(for: CGFloat.self,
                          of: { $0.size.width },
                          action: { containerWidth = $0 })
    }
}

struct DateItemView: View
{
    let date: Date

    private let formatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEEE HH:mm"
        
        return f
    }()
    
    var body: some View {
        VStack {
            Spacer(minLength: 34.0)
            
            Text(formatter.string(from: date))
                .font(.caption)
                .foregroundStyle(.secondary)
                .padding(.vertical, 4)
            
            Spacer()
        }
    }
}

struct SenderAttributionView: View
{
    let sender: Display.Sender
    
    var body: some View {
        HStack {
            if sender.isMe { Spacer() }
            
            Text(sender.displayName)
                .foregroundStyle(.secondary)
                .font(.caption2)
            
            if !sender.isMe { Spacer() }
        }
        .padding(.top, 10.0)
    }
}

fileprivate extension CGFloat
{
    static let dominantCornerRadius = 16.0
    static let minorCornerRadius = 4.0
    static let minimumBubbleHorizontalPadding = 80.0
    static let imageMaxWidth = 380.0
}

fileprivate extension CGSize
{
    var aspectRatio: CGFloat { width / height }
}

fileprivate func preferredBubbleWidth(forAttachmentSize attachmentSize: CGSize?, containerWidth: CGFloat?, maxWidth: CGFloat) -> CGFloat {
    if let containerWidth, let attachmentWidth = attachmentSize?.width {
        return .minimum(maxWidth, .minimum(containerWidth, attachmentWidth))
    } else if let containerWidth {
        return containerWidth
    } else {
        return 200.0 // fallback
    }
}

fileprivate extension String
{
    func linkifiedAttributedString() -> AttributedString {
        var attributed = AttributedString(self)
        guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
            return attributed
        }

        let nsText = self as NSString
        let fullRange = NSRange(location: 0, length: nsText.length)
        detector.enumerateMatches(in: self, options: [], range: fullRange) { result, _, _ in
            guard let result, let url = result.url,
                  let swiftRange = Range(result.range, in: self),
                  let start = AttributedString.Index(swiftRange.lowerBound, within: attributed),
                  let end = AttributedString.Index(swiftRange.upperBound, within: attributed) else { return }

            attributed[start..<end].link = url
            attributed[start..<end].foregroundColor = NSColor.textColor
            attributed[start..<end].underlineStyle = .single
        }

        return attributed
    }
}
