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

import SwiftUI
import SimpleXChat

let notesChatColorLight = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.21)
let notesChatColorDark = Color(.sRGB, red: 0.27, green: 0.72, blue: 1, opacity: 0.19)
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 {
    @EnvironmentObject var m: ChatModel
    @Environment(\.colorScheme) var colorScheme
    @ObservedObject var chat: Chat
    var chatItem: ChatItem
    @Binding var revealed: Bool
    var maxWidth: CGFloat = .infinity
    @State var scrollProxy: ScrollViewProxy? = nil
    @State var msgWidth: CGFloat = 0
    @State var imgWidth: CGFloat? = nil
    @State var videoWidth: CGFloat? = nil
    @State var metaColor = Color.secondary
    @State var showFullScreenImage = false
    @Binding var allowMenu: Bool
    @State private var showSecrets = false
    @State private var showQuoteSecrets = false

    @Binding var audioPlayer: AudioPlayer?
    @Binding var playbackState: VoiceMessagePlaybackState
    @Binding var playbackTime: TimeInterval?

    var body: some View {
        let v = ZStack(alignment: .bottomTrailing) {
            VStack(alignment: .leading, spacing: 0) {
                if let di = chatItem.meta.itemDeleted {
                    switch di {
                    case let .moderated(_, byGroupMember):
                        framedItemHeader(icon: "flag", caption: Text("moderated by \(byGroupMember.displayName)").italic())
                    case .blocked:
                        framedItemHeader(icon: "hand.raised", caption: Text("blocked").italic())
                    case .blockedByAdmin:
                        framedItemHeader(icon: "hand.raised", caption: Text("blocked by admin").italic())
                    case .deleted:
                        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 = m.reversedChatItems.first(where: { $0.id == qi.itemId }) {
                                withAnimation {
                                    proxy.scrollTo(ci.viewId, anchor: .bottom)
                                }
                            }
                        }
                }

                ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: framedMsgContentView)
                    .padding(chatItem.content.msgContent != nil ? 0 : 4)
                    .overlay(DetermineWidth())
            }
            .onPreferenceChange(MetaColorPreferenceKey.self) { metaColor = $0 }

            if chatItem.content.msgContent != nil {
                CIMetaView(chat: chat, chatItem: chatItem, metaColor: metaColor)
                    .padding(.horizontal, 12)
                    .padding(.bottom, 6)
                    .overlay(DetermineWidth())
                    .accessibilityLabel("")
            }
        }
            .background(chatItemFrameColorMaybeImageOrVideo(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, metaColor: metaColor)
                    .overlay(DetermineWidth())
                if text == "" && !chatItem.meta.isLive {
                    Color.clear
                        .frame(width: 0, height: 0)
                        .preference(
                            key: MetaColorPreferenceKey.self,
                            value: .white
                        )
                } else {
                    ciMsgContentView(chatItem)
                }
            case let .video(text, image, duration):
                CIVideoView(chatItem: chatItem, image: image, duration: duration, maxWidth: maxWidth, videoWidth: $videoWidth, 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)
                }
            case let .voice(text, duration):
                FramedCIVoiceView(chatItem: chatItem, recordingFile: chatItem.file, duration: duration, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
                    .overlay(DetermineWidth())
                if text != "" {
                    ciMsgContentView(chatItem)
                }
            case let .file(text):
                ciFileView(chatItem, text)
            case let .link(_, preview):
                CILinkView(linkPreview: preview)
                ciMsgContentView(chatItem)
            case let .unknown(_, text: text):
                if chatItem.file == nil {
                    ciMsgContentView(chatItem)
                } else {
                    ciFileView(chatItem, text)
                }
            default:
                ciMsgContentView(chatItem)
            }
        }
    }
    
    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 mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
            v.frame(maxWidth: mediaWidth, 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 let .video(_, 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)
            }
        }
            // if enable this always, size of the framed voice message item will be incorrect after end of playback
            .overlay { if case .voice = chatItem.content.msgContent {} else { DetermineWidth() } }
            .frame(minWidth: msgWidth, alignment: .leading)
            .background(chatItemFrameContextColor(chatItem, colorScheme))

        if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
            v.frame(maxWidth: mediaWidth, alignment: .leading)
        } else {
            v
        }
    }
    
    private func ciQuotedMsgView(_ qi: CIQuote) -> some View {
        Group {
            if let sender = qi.getSender(membership()) {
                VStack(alignment: .leading, spacing: 2) {
                    Text(sender).font(.caption).foregroundColor(.secondary)
                    ciQuotedMsgTextView(qi, lines: 2)
                }
            } else {
                ciQuotedMsgTextView(qi, lines: 3)
            }
        }
        .padding(.top, 6)
        .padding(.horizontal, 12)
    }
    
    private func ciQuotedMsgTextView(_ qi: CIQuote, lines: Int) -> some View {
        toggleSecrets(qi.formattedText, $showQuoteSecrets,
            MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, showSecrets: showQuoteSecrets)
                .lineLimit(lines)
                .font(.subheadline)
                .padding(.bottom, 6)
        )
    }

    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 chat.chatInfo {
        case let .group(groupInfo: groupInfo): return groupInfo.membership
        default: return nil
        }
    }
    
    @ViewBuilder private func ciMsgContentView(_ ci: ChatItem) -> some View {
        let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text
        let rtl = isRightToLeft(text)
        let ft = text == "" ? [] : ci.formattedText
        let v = toggleSecrets(ft, $showSecrets, MsgContentView(
            chat: chat,
            text: text,
            formattedText: ft,
            meta: ci.meta,
            rightToLeft: rtl,
            showSecrets: showSecrets
        ))
        .multilineTextAlignment(rtl ? .trailing : .leading)
        .padding(.vertical, 6)
        .padding(.horizontal, 12)
        .overlay(DetermineWidth())
        .frame(minWidth: 0, alignment: .leading)
        .textSelection(.enabled)

        if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth {
            v.frame(maxWidth: mediaWidth, 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)
        }
    }

    private func maxMediaWidth() -> CGFloat? {
        if let imgWidth = imgWidth, let videoWidth = videoWidth {
            return imgWidth > videoWidth ? imgWidth : videoWidth
        } else if let imgWidth = imgWidth {
            return imgWidth
        } else {
            return videoWidth
        }
    }
}

@ViewBuilder func toggleSecrets<V: View>(_ ft: [FormattedText]?, _ showSecrets: Binding<Bool>, _ v: V) -> some View {
    if let ft = ft, ft.contains(where: { $0.isSecret }) {
        v.onTapGesture { showSecrets.wrappedValue.toggle() }
    } else {
        v
    }
}

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 onlyImageOrVideo(_ ci: ChatItem) -> Bool {
    if case let .image(text, _) = ci.content.msgContent {
        return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && text == ""
    } else if case let .video(text, _, _) = ci.content.msgContent {
        return ci.meta.itemDeleted == nil && !ci.meta.isLive && ci.quotedItem == nil && text == ""
    }
    return false
}

func chatItemFrameColorMaybeImageOrVideo(_ ci: ChatItem, _ colorScheme: ColorScheme) -> Color {
    onlyImageOrVideo(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(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}

struct FramedItemView_Edited_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, 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]"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, 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]"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}

struct FramedItemView_Deleted_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, 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]"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
            FramedItemView(chat: Chat.sampleData, 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]"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil))
        }
        .previewLayout(.fixed(width: 360, height: 200))
    }
}
