//
//  FramedItemView.swift
//  SimpleX
//
//  Created by Evgeny Poberezkin on 04/02/2022.
//  Copyright © 2022 SimpleX Chat. All rights reserved.
//

import SwiftUI
import SimpleXChat

let sentColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.12)
let sentColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.17)
private let sentQuoteColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.11)
private let sentQuoteColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.09)

struct FramedItemView: View {
    @Environment(\.colorScheme) var colorScheme
    var chatInfo: ChatInfo
    var chatItem: ChatItem
    var showMember = false
    var maxWidth: CGFloat = .infinity
    @State var scrollProxy: ScrollViewProxy? = nil
    @State var msgWidth: CGFloat = 0
    @State var imgWidth: CGFloat? = nil
    @State var metaColor = Color.secondary
    @State var showFullScreenImage = false
    
    var body: some View {
        let v = ZStack(alignment: .bottomTrailing) {
            VStack(alignment: .leading, spacing: 0) {
                if chatItem.meta.itemDeleted {
                    framedItemHeader(icon: "trash", caption: Text("marked deleted").italic())
                } else if chatItem.meta.isLive {
                    framedItemHeader(caption: Text("LIVE"))
                }
                
                if let qi = chatItem.quotedItem {
                    ciQuoteView(qi)
                        .onTapGesture {
                            if let proxy = scrollProxy,
                               let ci = ChatModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
                                withAnimation {
                                    proxy.scrollTo(ci.viewId, anchor: .bottom)
                                }
                            }
                        }
                }
                
                ChatItemContentView(chatInfo: chatInfo, chatItem: chatItem, showMember: showMember, msgContentView: framedMsgContentView)
                    .padding(chatItem.content.msgContent != nil ? 0 : 4)
                    .overlay(DetermineWidth())
            }
            .onPreferenceChange(MetaColorPreferenceKey.self) { metaColor = $0 }
            
            if chatItem.content.msgContent != nil {
                CIMetaView(chatItem: chatItem, metaColor: metaColor)
                    .padding(.horizontal, 12)
                    .padding(.bottom, 6)
                    .overlay(DetermineWidth())
            }
        }
            .background(chatItemFrameColorMaybeImage(chatItem, colorScheme))
            .cornerRadius(18)
            .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 }
        
        switch chatItem.meta.itemStatus {
        case .sndErrorAuth:
            v.onTapGesture { msgDeliveryError("Most likely this contact has deleted the connection with you.") }
        case let .sndError(agentError):
            v.onTapGesture { msgDeliveryError("Unexpected error: \(agentError)") }
        default: v
        }
    }
    
    @ViewBuilder private func framedMsgContentView() -> some View {
        if chatItem.formattedText == nil && chatItem.file == nil && !chatItem.meta.isLive && isShortEmoji(chatItem.content.text) {
            VStack {
                emojiText(chatItem.content.text)
                Text("")
            }
            .padding(.vertical, 6)
            .padding(.horizontal, 12)
            .overlay(DetermineWidth())
            .frame(minWidth: msgWidth, alignment: .center)
            .padding(.bottom, 2)
        } else {
            switch (chatItem.content.msgContent) {
            case let .image(text, image):
                CIImageView(chatItem: chatItem, image: image, maxWidth: maxWidth, imgWidth: $imgWidth, scrollProxy: scrollProxy)
                    .overlay(DetermineWidth())
                if text == "" && !chatItem.meta.isLive {
                    Color.clear
                        .frame(width: 0, height: 0)
                        .preference(
                            key: MetaColorPreferenceKey.self,
                            value: .white
                        )
                } else {
                    ciMsgContentView (chatItem, showMember)
                }
            case let .voice(text, duration):
                FramedCIVoiceView(chatItem: chatItem, recordingFile: chatItem.file, duration: duration)
                    .overlay(DetermineWidth())
                if text != "" {
                    ciMsgContentView (chatItem, showMember)
                }
            case let .file(text):
                ciFileView(chatItem, text)
            case let .link(_, preview):
                CILinkView(linkPreview: preview)
                ciMsgContentView (chatItem, showMember)
            case let .unknown(_, text: text):
                if chatItem.file == nil {
                    ciMsgContentView (chatItem, showMember)
                } else {
                    ciFileView(chatItem, text)
                }
            default:
                ciMsgContentView (chatItem, showMember)
            }
        }
    }
    
    private func msgDeliveryError(_ err: LocalizedStringKey) {
        AlertManager.shared.showAlertMsg(
            title: "Message delivery error",
            message: err
        )
    }

    @ViewBuilder func framedItemHeader(icon: String? = nil, caption: Text) -> some View {
        let v = HStack(spacing: 6) {
            if let icon = icon {
                Image(systemName: icon)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 14, height: 14)
            }
            caption
                .font(.caption)
                .lineLimit(1)
        }
        .foregroundColor(.secondary)
        .padding(.horizontal, 12)
        .padding(.top, 6)
        .padding(.bottom, chatItem.quotedItem == nil ? 6 : 0) // TODO think how to regroup
        .overlay(DetermineWidth())
        .frame(minWidth: msgWidth, alignment: .leading)
        .background(chatItemFrameContextColor(chatItem, colorScheme))
        if let imgWidth = imgWidth, imgWidth < maxWidth {
            v.frame(maxWidth: imgWidth, alignment: .leading)
        } else {
            v
        }
    }

    @ViewBuilder private func ciQuoteView(_ qi: CIQuote) -> some View {
        let v = ZStack(alignment: .topTrailing) {
            switch (qi.content) {
            case let .image(_, image):
                if let data = Data(base64Encoded: dropImagePrefix(image)),
                   let uiImage = UIImage(data: data) {
                    ciQuotedMsgView(qi)
                        .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading)
                    Image(uiImage: uiImage)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 68, height: 68)
                        .clipped()
                } else {
                    ciQuotedMsgView(qi)
                }
            case .file:
                ciQuotedMsgView(qi)
                    .padding(.trailing, 20).frame(minWidth: msgWidth, alignment: .leading)
                ciQuoteIconView("doc.fill")
            case .voice:
                ciQuotedMsgView(qi)
                    .padding(.trailing, 20).frame(minWidth: msgWidth, alignment: .leading)
                ciQuoteIconView("mic.fill")
            default:
                ciQuotedMsgView(qi)
            }
        }
            .overlay(DetermineWidth())
            .frame(minWidth: msgWidth, alignment: .leading)
            .background(chatItemFrameContextColor(chatItem, colorScheme))
        
        if let imgWidth = imgWidth, imgWidth < maxWidth {
            v.frame(maxWidth: imgWidth, alignment: .leading)
        } else {
            v
        }
    }
    
    private func ciQuotedMsgView(_ qi: CIQuote) -> some View {
        MsgContentView(
            text: qi.text,
            formattedText: qi.formattedText,
            sender: qi.getSender(membership())
        )
        .lineLimit(3)
        .font(.subheadline)
        .padding(.vertical, 6)
        .padding(.horizontal, 12)
    }
    
    private func ciQuoteIconView(_ image: String) -> some View {
        Image(systemName: image)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .foregroundColor(Color(uiColor: .tertiaryLabel))
            .frame(width: 18, height: 18)
            .padding(.top, 6)
            .padding(.trailing, 6)
    }
    
    private func membership() -> GroupMember? {
        switch chatInfo {
        case let .group(groupInfo: groupInfo): return groupInfo.membership
        default: return nil
        }
    }
    
    @ViewBuilder private func ciMsgContentView(_ ci: ChatItem, _ showMember: Bool = false) -> some View {
        let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text
        let rtl = isRightToLeft(text)
        let v = MsgContentView(
            text: text,
            formattedText: text == "" ? [] : ci.formattedText,
            sender: showMember ? ci.memberDisplayName : nil,
            meta: ci.meta,
            rightToLeft: rtl
        )
        .multilineTextAlignment(rtl ? .trailing : .leading)
        .padding(.vertical, 6)
        .padding(.horizontal, 12)
        .overlay(DetermineWidth())
        .frame(minWidth: 0, alignment: .leading)
        .textSelection(.enabled)
        
        if let imgWidth = imgWidth, imgWidth < maxWidth {
            v.frame(maxWidth: imgWidth, alignment: .leading)
        } else {
            v
        }
    }
    
    @ViewBuilder private func ciFileView(_ ci: ChatItem, _ text: String) -> some View {
        CIFileView(file: chatItem.file, edited: chatItem.meta.itemEdited)
            .overlay(DetermineWidth())
        if text != "" || ci.meta.isLive {
            ciMsgContentView (chatItem, showMember)
        }
    }
}

func isRightToLeft(_ s: String) -> Bool {
    if let lang = CFStringTokenizerCopyBestStringLanguage(s as CFString, CFRange(location: 0, length: min(s.count, 80))) {
        return NSLocale.characterDirection(forLanguage: lang as String) == .rightToLeft
    }
    return false
}

private struct MetaColorPreferenceKey: PreferenceKey {
    static var defaultValue = Color.secondary
    static func reduce(value: inout Color, nextValue: () -> Color) {
        value = nextValue()
    }
}

func onlyImage(_ ci: ChatItem) -> Bool {
    if case let .image(text, _) = ci.content.msgContent {
        return !ci.meta.itemDeleted && !ci.meta.isLive && ci.quotedItem == nil && text == ""
    }
    return false
}

func chatItemFrameColorMaybeImage(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
    onlyImage(ci)
    ? Color.clear
    : chatItemFrameColor(ci, colorScheme)
}

func chatItemFrameColor(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
    ci.chatDir.sent
    ? (colorScheme == .light ? sentColorLight : sentColorDark)
    : Color(uiColor: .tertiarySystemGroupedBackground)
}

func chatItemFrameContextColor(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
    ci.chatDir.sent
    ? (colorScheme == .light ? sentQuoteColorLight : sentQuoteColorDark)
    : Color(uiColor: .quaternarySystemFill)
}

struct FramedItemView_Previews: PreviewProvider {
    static var previews: some View {
        Group{
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}

struct FramedItemView_Edited_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,[encoded data, 8224 characters]"), false, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,[encoded data, 8224 characters]"), false, true))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}

struct FramedItemView_Deleted_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent, quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,[encoded data, 8224 characters]"), true))
            FramedItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,[encoded data, 8224 characters]"), true))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}
