import APIClient
import CommentsClient
import ComponentLibrary
import ComposableArchitecture
import CoreMedia
import FeatureBrandedAlert
import FeatureCaptions
import FeatureToasts
import Localization
import StatsigClient
import SwiftUI
import Utilities

public struct CommentsSheetView<Item: Commentable>: View {
    @Bindable var store: StoreOf<CommentsSheetReducer<Item>>
    @State var currentDetent = PresentationDetent.medium

    @FocusState private var focusedTextField

    public init(store: StoreOf<CommentsSheetReducer<Item>>) {
        self.store = store
    }

    public var body: some View {
        VStack(spacing: .zero) {
            topBarView
                .frame(height: 42.0)
                .padding(.bottom, 8.0)

            divider

            mainContentStack
                .overlay(alignment: .bottom) {
                    ToastView(store: store.scope(state: \.toastState, action: \.toastAction))

                    replyOverlayView
                }
                .clipped()
        }
        .safeAreaInset(edge: .bottom, spacing: 0) {
            bottomBarView
        }
        .overlay {
            if let _ = store.state.brandedAlert.destination {
                ZStack {
                    Color.SemanticV1.backgroundQuaternary.opacity(0.4)
                        .ignoresSafeArea()
                    BrandedAlertView(store.scope(state: \.brandedAlert, action: \.brandedAlert))
                }
            }
        }
        .presentationDetents([.medium, .large], selection: $currentDetent)
        .task {
            store.send(.task)
        }
        .onAppear {
            store.send(.onAppear)
        }
        .onChange(of: store.currentReplyParentCommentID) { _, newValue in
            // Open keyboard when routing into a reply to a comment
            if newValue != nil {
                focusOnTextField()
            }
        }
    }
}

private extension CommentsSheetView {
    var loadingStack: some View {
        VStack {
            ProgressView()
                .progressViewStyle(.circular)
                .padding(.top, 64.0)
        }
    }

    @ViewBuilder
    var emptyStack: some View {
        let description = store.isClipCommentsSheet ? L10n.FeatureComments.emptyCommentsSheetDescriptionClip : L10n.FeatureComments.emptyCommentsSheetDescriptionHook
        VStack {
            Text(L10n.FeatureComments.exclaimNoCommentsYet)
                .typographyV1(.headline3)
                .multilineTextAlignment(.center)
            Text(description)
                .typographyV1(.caption2)
                .frame(width: 250.0)
                .multilineTextAlignment(.center)
        }
        .foregroundStyle(Color.SemanticV1.textPrimary)
    }

    var divider: some View {
        Divider()
            .foregroundStyle(Color.SemanticV1.textTertiary)
    }

    @ViewBuilder
    var mainContentStack: some View {
        let pinnedCaptionBackgroundColor = store.isClipCommentsSheet ? Color(light: #colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1), dark: #colorLiteral(red: 0.1960784314, green: 0.1960784314, blue: 0.1960784314, alpha: 1)) : Color.SemanticV2.backgroundFogThin

        ScrollView {
            Color.clear
                .frame(height: 22.0)

            if store.isLoading {
                loadingStack
            } else if store.noCommentsAndNoAuthorCaption {
                emptyStack
                    .padding(.top, 64.0)
                    .opacity(.half)
            } else {
                if let caption = store.authorCaption {
                    PinnedCaptionView(
                        caption,
                        backgroundColor: pinnedCaptionBackgroundColor,
                        userMentionTapped: { handle in
                            store.send(.didTapUserHandle(handle))
                        }
                    )
                    .padding(.top, -20)
                }
                scrollingCommentsThread
            }

            if store.hasReplyComment {
                Color.clear
                    .frame(height: 42.0)
            }
        }
        .scrollDismissesKeyboard(.automatic)
    }
}

private extension CommentsSheetView {
    var topBarView: some View {
        HStack {
            Group {
                switch store.totalCommentCount {
                case .known(let count):
                    if count == .zero {
                        Text(L10n.FeatureComments.comments)
                            .typographyV1(.caption2)
                    } else {
                        Text(L10n.FeatureComments.numComments(count))
                            .typographyV1(.caption2)
                    }

                case .unknown:
                    Text(L10n.FeatureComments.comments)
                        .typographyV1(.caption2)
                }
            }
            .padding(.top, 24.0)
        }
    }
}

private extension CommentsSheetView {
    var scrollingCommentsThread: some View {
        LazyVStack {
            ForEach(store.pendingComments, id: \.id) { comment in
                // Not using commentComponent helper function
                // here since it is a highly limited version
                CommentSheetItem(
                    comment,
                    isCommentByCurrentUser: comment.userHandle == store.currentUser.handle,
                    isEntityByCurrentUser: store.isEntityAuthorCurrentUser,
                    isCommentByEntityAuthor: comment.userHandle == store.item.authorUserHandle,
                    isLoading: true,
                    tapAuthor: {
                        store.send(.didTapUserHandle(comment.userHandle))
                    },
                    tapMentionedUser: { userHandle in
                        store.send(.didTapUserHandle(userHandle))
                    }
                )
                .padding(.bottom, 16.0)
                .opacity(.half)
            }

            ForEach(store.comments, id: \.id) { comment in
                let replies = store.replyCommentsMap[comment.id] ?? []
                let hasMoreReplies = store.hasMoreRepliesMap[comment.id] ?? false

                commentComponent(
                    comment: comment,
                    replies: replies,
                    hasMoreReplies: hasMoreReplies
                )
                .padding(.bottom, replies.isEmpty ? 16.0 : 8.0)

                ForEach(replies, id: \.id) { reply in
                    let isLastReply = reply.id == replies.last?.id
                    let isFirstReply = reply.id == replies.first?.id
                    let remainingReplies = max(0, comment.numReplies - replies.count)
                    commentComponent(
                        comment: reply,
                        hasMoreReplies: comment.numReplies > replies.count && hasMoreReplies,
                        parentReplyCount: remainingReplies,
                        isFirstReply: isFirstReply,
                        isLastReply: isLastReply,
                        onTapLoadMoreReplies: {
                            store.send(.loadNextReplyPageOfComments(comment.id))
                        }
                    )
                    .padding(.leading, 24.0)
                    .padding(.bottom, isLastReply ? 8.0 : 0.0)
                }
            }

            // Bottom section to handle pagination
            // TODO: Revisit how we're doing pre-fetching and investigate why calling .onItemAppeared causes scroll to lag so much
            if !store.isRequestingComments, !store.comments.isEmpty {
                Color.clear
                    .frame(height: 1)
                    .onAppear {
                        // Trigger with the last index to reuse existing reducer guard logic
                        let lastIndex = max(0, store.comments.count - 1)
                        store.send(.onItemAppeared(lastIndex))
                    }
                    .id("load-more-comments-\(store.comments.count)")
            }

            Color.clear
                .frame(height: 32.0)
        }
        .padding(.leading, 16.0)
        .padding(.trailing, 8.0)
    }

    @ViewBuilder
    func commentComponent(
        comment: CommentEntity,
        replies _: [CommentEntity] = [],
        hasMoreReplies: Bool = false,
        parentReplyCount: Int = .zero,
        isFirstReply: Bool = false,
        isLastReply: Bool = false,
        onTapLoadMoreReplies: @escaping () -> Void = {},
        onAppear: @escaping () -> Void = {}
    ) -> some View {
        CommentSheetItem(
            comment,
            isCommentByCurrentUser: comment.userHandle == store.currentUser.handle,
            isEntityByCurrentUser: store.isEntityAuthorCurrentUser,
            isCommentByEntityAuthor: comment.userHandle == store.item.authorUserHandle,
            parentReplyCount: parentReplyCount,
            isFirstReply: isFirstReply,
            isLastReply: isLastReply,
            hasMoreReplies: hasMoreReplies,
            hasExpandedLongComment: store.hasExpandedComment[comment.id] ?? false,
            tapAuthor: {
                store.send(.didTapUserHandle(comment.userHandle))
            },
            tapMentionedUser: { userHandle in
                store.send(.didTapUserHandle(userHandle))
            },
            tapReaction: {
                switch comment.reaction {
                case .like:
                    store.send(.updateCommentReaction(comment.id, store.item.commentEntityId, comment.entityType, .removeReaction, comment.numLikes))
                case .dislike, .removeReaction:
                    store.send(.updateCommentReaction(comment.id, store.item.commentEntityId, comment.entityType, .like, comment.numLikes))
                }
            },
            tapReplyAction: {
                store.send(.setReplyTarget(comment.id))
            },
            tapLoadMoreReplies: {
                onTapLoadMoreReplies()
            },
            tapExpandOnLongComment: {
                store.send(.expandedComment(comment.id))
            },
            reportAction: { reason in
                store.send(.reportComment(comment.id, store.item.commentEntityId, comment.entityType, reason))
            },
            deleteAction: {
                store.send(.deleteComment(comment.id, store.item.commentEntityId, comment.entityType))
            },
            onAppear: onAppear
        )
        .equatable()
    }
}

private extension CommentsSheetView {
    @ViewBuilder
    var replyOverlayView: some View {
        let backgroundColor: Color = store.isClipCommentsSheet ? .SemanticV1.backgroundTertiary : .SemanticV2.backgroundFogThin

        CommentReplyOverlayView(
            hasReplyComment: store.hasReplyComment,
            replyRootCommentUserDisplayName: store.replyRootCommentUserDisplayName,
            replyRootCommentContent: store.replyRootCommentContent,
            onClearReplyTarget: {
                store.send(.clearReplyTarget)
            },
            backgroundColor: backgroundColor
        )
    }
}

private extension CommentsSheetView {
    var bottomBarView: some View {
        VStack(spacing: .zero) {
            divider
            ZStack {
                if store.areCommentsEnabled {
                    bottomBarContent
                } else {
                    bottomBarDisabledContent
                }
            }
            .opacity(store.isLoading ? 0 : 1)
        }
        .clipped()
        .animation(.spring(duration: 0.25), value: store.state.userMentionSearchSuggestions.isEmpty)
    }

    @ViewBuilder
    var bottomBarDisabledContent: some View {
        let description = store.isClipCommentsSheet ? L10n.FeatureComments.commentsSheetDisabledClip : L10n.FeatureComments.commentsSheetDisabledHook
        let backgroundColor: Color = store.isClipCommentsSheet ? .SemanticV1.backgroundQuaternary : .SemanticV2.backgroundFogThin

        backgroundColor
            .frame(height: 64.0)
            .ignoresSafeArea()
        Text(description)
            .typographyV1(.bodySmall)
            .foregroundStyle(Color.SemanticV1.textTertiary)
    }

    var bottomBarContent: some View {
        VStack(spacing: .zero) {
            if !store.state.userMentionSearchSuggestions.isEmpty {
                userMentionSearchList
                    .transition(.blurReplace)
            } else {
                emojiScrollArea
                    .padding(.top, store.areCommentsEnabled ? 8.0 : 0.0)
                    .padding(.bottom, 8.0)
                    .transition(.blurReplace)
            }
            textFieldStack
                .padding(.horizontal, 16.0)
                .frame(height: 64.0)
        }
    }

    var textFieldStack: some View {
        HStack {
            userAvatarImage

            textFieldButton

            openUserMentionSearchButton
        }
    }

    @ViewBuilder
    var textFieldButton: some View {
        ZStack {
            Button(action: focusOnTextField) {}
                .buttonStyle(PillButtonStyleV1(size: .textFieldButton, colorCombination: .textFieldButton))
                .accessibilityLabel(L10n.FeatureComments.ellipsesAddAComment)
                .accessibilityHidden(focusedTextField)
            textFieldCapsule
                .allowsHitTesting(focusedTextField)
                .accessibilityHidden(!focusedTextField)
        }
    }

    @ViewBuilder
    var textFieldCapsule: some View {
        let showingQuickSendButton = !store.userCommentText.isEmpty

        HStack(spacing: 0) {
            textField
                .padding(.leading, 16)
                .padding(.trailing, -8)

            if store.isClipCommentsSheet {
                timeElapsedText
                    .padding(.trailing, showingQuickSendButton ? -4 : 10)
                    .padding(.leading, 10)
            }

            if showingQuickSendButton {
                quickSendButton
                    /// to align exactly with the rounded rectangle background of the `textFieldStack`
                        .offset(x: 5)
            }
        }
        .frame(height: 44.0)
    }

    /// Required to avoid visual artifacts when focusing on text field
    func focusOnTextField() {
        Task {
            if currentDetent == .medium {
                currentDetent = .large
                try? await Task.sleep(for: .seconds(0.05))
            }
            focusedTextField = true
        }
    }

    @ViewBuilder
    var emojiScrollArea: some View {
        let emojis: [String] = [
            "🔥", "😍", "😱", "🙌", "👍", "👎", "🥵", "😎",
            "😄", "😁", "🎉", "😂", "🎊", "👏", "🕺", "😊",
            "💃", "🤣", "😃", "😀", "🤗", "😆", "🥳", "🤩",
            "🥰",
        ]
        CommentEmojiBar(emojis: emojis) { emoji in
            store.send(.addSingleEmoji(emoji))
        }
    }

    @ViewBuilder
    var userAvatarImage: some View {
        RemoteImage(url: store.currentUser.avatarImageUrl, fallbackId: "1")
            .frame(width: 40, height: 40)
            .clipShape(Circle())
    }

    @ViewBuilder
    var textField: some View {
        TextField(
            "",
            text: $store.userCommentText.limit(CommentsClientV2.commentLengthLimit),
            prompt: Text(L10n.FeatureComments.ellipsesAddAComment),
            axis: .vertical
        )
        .typographyV1(.caption)
        .lineLimit(3)
        .submitLabel(.send)
        .focused($focusedTextField)
        .onSubmit {
            store.send(.postComment)
        }
        .onChange(of: store.userCommentText) { oldValue, newValue in
            store.send(.onUserCommentTextUpdated(oldValue: oldValue, newValue: newValue))
        }
    }

    @ViewBuilder
    var timeElapsedText: some View {
        Text(store.commentTrackTime.format_m_ss)
            .typographyV1(.caption)
            .foregroundColor(.SemanticV2.foregroundInactive)
            .lineLimit(1)
    }

    @ViewBuilder
    var quickSendButton: some View {
        Button {
            store.send(.postComment)
        } label: {
            Image.Icon.backButtonV1.rotationEffect(.degrees(90.0))
                .foregroundStyle(Color.SemanticV1.textInvert)
                .padding(.horizontal, 16.0)
                .frame(height: 38)
                .background(Color.SemanticV1.textPrimary)
                .clipShape(Circle())
        }
        .buttonStyle(.plain)
    }
}

private extension CommentsSheetView {
    var userMentionSearchList: some View {
        let count = store.userMentionSearchSuggestions.count
        let isScrollable = count >= 4
        let itemHeight: CGFloat = 56
        let topBottomPadding: CGFloat = isScrollable ? 8 : 6
        let listHeight = CGFloat(count) * itemHeight + (topBottomPadding * 2)

        return ScrollView {
            LazyVStack(spacing: .zero) {
                ForEach(Array(store.userMentionSearchSuggestions.enumerated()), id: \.element) { index, user in
                    let isMiddleItem = index < count - 1

                    // TODO: At some point, a Button's click action was causing issues but now it's not. Revisit and possibly refactor later.
                    userMentionSearchListItem(user, showDivider: isMiddleItem)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal, 16)
                        .contentShape(Rectangle())
                        // Use tap gesture with simultaneous drag gesture to avoid accidentally
                        // selecting a list item when lifting finger off list
                        .onTapGesture {
                            store.send(.onUserMentionSearchItemTapped(user))
                        }
                }
            }
            .padding(.vertical, topBottomPadding)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        }
        .background(Color.SemanticV2.backgroundFogThin)
        .frame(height: min(listHeight, UIScreen.height * 0.3))
        .overlay(alignment: .bottom) {
            if isScrollable {
                LinearGradient(colors: [.black.opacity(0), .black], startPoint: .top, endPoint: .bottom)
                    .blendMode(.destinationOut)
                    .frame(height: 40)
                    .allowsHitTesting(false)
            }
        }
        .compositingGroup()
    }

    func userMentionSearchListItem(_ user: SimpleProfile, showDivider: Bool = true) -> some View {
        VStack(spacing: .zero) {
            HStack(spacing: 12) {
                RemoteImage(url: user.avatarImageUrl, fallbackId: nil)
                    .frame(width: 32, height: 32)
                    .clipShape(Circle())

                VStack(alignment: .leading, spacing: .zero) {
                    if !user.displayName.isEmpty {
                        Text(user.displayName)
                            .typographyV1(.body3)
                            .foregroundColor(.SemanticV1.textPrimary)
                            .lineLimit(1)
                    }
                    Text("@" + user.handle)
                        .typographyV1(.caption)
                        .foregroundColor(.SemanticV1.textTertiary)
                        .lineLimit(1)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
            }
            .contentShape(.rect)
            .padding(.vertical, 10)

            if showDivider {
                divider
            }
        }
    }

    var openUserMentionSearchButton: some View {
        Button {
            store.send(.openUserMentionSearch)
            focusOnTextField()
        } label: {
            Image.Icon.at
                .renderingMode(.template)
                .foregroundStyle(Color.SemanticV1.textTertiary)
                .padding(.horizontal, 10.0)
                .frame(height: 36)
                .clipShape(Circle())
        }
        .buttonStyle(.plain)
    }
}
