import APIClient
import ComponentLibrary
import ComposableArchitecture
import Errors
import FeatureBrandedAlert
import FeatureEditSongArt
import FeatureToasts
import GenAPI
import Localization
import StatsigClient
import SwiftUI
import Utilities

// swiftlint:disable file_length

public struct SongDetailsView<Mode: SongDetailsMode>: View {
    @Bindable var store: StoreOf<SongDetailsReducer<Mode>>
    @FocusState var focusedField: SongDetailsState<Mode>.Field?
    @State private var name: String
    @State private var caption: String
    @State private var styleSummary: String

    // These are necessary because of a SwiftUI bug that doesn't automatically
    // update style when the underlying value changes. We use them in `onChange`
    // to update the colors when the text is not empty.
    @State var nameFieldColor: Color
    @State var captionFieldColor: Color

    private let captionCharacterLimit = 500

    private enum ScrollTarget: String, CaseIterable {
        case captionField
        case userMentionDropdown
    }

    public init(store: StoreOf<SongDetailsReducer<Mode>>) {
        self.store = store
        self._name = State(initialValue: store.nameField.currentValue)
        self._caption = State(initialValue: store.captionField.currentValue)
        self._styleSummary = State(initialValue: store.styleSummaryField.currentValue)
        self._nameFieldColor = State(initialValue: store.nameField.currentValue.isEmpty ? Color.SemanticV2.foregroundInactive : Color.SemanticV2.foregroundPrimary)
        self._captionFieldColor = State(initialValue: store.captionField.currentValue.isEmpty ? Color.SemanticV2.foregroundInactive : Color.SemanticV2.foregroundPrimary)
    }

    public var body: some View {
        NavigationStack {
            mainContent
                .navigationDestination(item: $store.scope(state: \.destination?.editDisplayedLyrics, action: \.destination.editDisplayedLyrics)) { store in
                    EditDisplayedLyricsView(store: store)
                        .navigationBarBackButtonHidden(true)
                }
                .navigationDestination(item: $store.scope(state: \.destination?.editDisplayStyles, action: \.destination.editDisplayStyles)) { store in
                    EditDisplayStylesView(store: store)
                        .navigationBarBackButtonHidden(true)
                }
                .navigationDestination(item: $store.scope(state: \.destination?.replaceSongArt, action: \.destination.replaceSongArt)) { store in
                    ReplaceSongArtScreen(store: store)
                        .navigationBarBackButtonHidden(true)
                }
                .navigationDestination(item: $store.scope(state: \.destination?.moreOptions, action: \.destination.moreOptions)) { store in
                    MoreOptionsView(store: store)
                        .navigationBarBackButtonHidden(true)
                }
        }
        .bind($store.focusedField, to: $focusedField)
        .sensoryFeedbackIfEnabled(.success, trigger: store.toggleHapticSuccess)
        .sensoryFeedbackIfEnabled(.warning, trigger: store.toggleHapticError)
        .background(Color.SemanticV1.backgroundPrimary)
        .alert($store.scope(state: \.destination?.alert, action: \.destination.alert))
        .brandedAlert(error: $store.error) {
            store.send(.clearError)
        }
        .toast($store.toast, position: .bottom, padding: EdgeInsets(bottom: 18))
        .task {
            store.send(.task)
        }
    }

    @ViewBuilder
    private var mainContent: some View {
        ScrollViewReader { proxy in
            ScrollView(showsIndicators: false) {
                VStack(spacing: .zero) {
                    header
                    nameField
                    captionSection
                        .id(ScrollTarget.captionField.rawValue)
                    menuOptions
                    Spacer()
                }
            }
            .safeAreaInset(edge: .bottom) {
                bottomSubmitButton
            }
            .scrollBounceBehavior(.basedOnSize)
            .scrollDismissesKeyboard(.interactively)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                toolbarContent
            }
            .background(Color.SemanticV1.backgroundPrimary)
            .onTapGesture {
                focusedField = nil
            }
            .onChange(of: focusedField) { _, newValue in
                // Scroll when caption field is focused
                if newValue == .caption {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                        withAnimation(.easeInOut(duration: 0.3)) {
                            proxy.scrollTo(ScrollTarget.captionField.rawValue, anchor: .center)
                        }
                    }
                }
            }
            .onChange(of: store.userMentionSearchSuggestions) { _, suggestions in
                // Scroll to show dropdown when suggestions appear
                if focusedField == .caption && !suggestions.isEmpty {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                        withAnimation(.easeInOut(duration: 0.3)) {
                            proxy.scrollTo(ScrollTarget.userMentionDropdown.rawValue, anchor: .center)
                        }
                    }
                }
            }
            .onChange(of: store.captionField.currentValue) { _, newValue in
                if caption != newValue {
                    // Sync store caption changes back to view's local state
                    caption = newValue
                }
            }
        }
    }

    @ViewBuilder
    private var bottomSubmitButton: some View {
        if let submitLabel = Mode.submitButtonTitle, focusedField == nil {
            submitButton(label: submitLabel)
                .transition(.opacity)
        }
    }

    @ToolbarContentBuilder
    private var toolbarContent: some ToolbarContent {
        ToolbarItem(placement: .topBarLeading) {
            dismissButton
        }
        ToolbarItem(placement: .principal) {
            navigationTitle
        }
        ToolbarItem(placement: .topBarTrailing) {
            saveButton
        }
    }

    @ViewBuilder
    private var menuOptions: some View {
        VStack(spacing: .zero) {
            Divider()

            if let parentClip = store.parentClip, FeatureFlag.create.remixAndAttribution {
                remixOriginRow(parentClip: parentClip)
            }

            if store.clip.highLevelModel == .v4_5 || store.clip.highLevelModel == .v4_5Plus || store.clip.highLevelModel == .v5 {
                displayStylesButton
            }
            lyricsButton
            optionsButton
                .opacity(store.showMoreOptions ? 1 : 0)
        }
        .padding(.horizontal, 12)
    }

    private var textWidth: CGFloat {
        let font = TypographyV1.headline4.size { _ in 18.0 }.lineHeight(30.0).uiFont ?? UIFont.systemFont(ofSize: 18.0, weight: .medium)
        var text = name
        if text.isEmpty {
            // Use placeholder text width when we're showing it
            text = L10n.FeatureManageClip.titlePlaceholder
        }
        let textWidth = text.boundingBox(with: font).width
        return textWidth + 8
    }

    private var nameField: some View {
        TextField(L10n.FeatureManageClip.titlePlaceholder, text: $name)
            .focused($focusedField, equals: .name)
            .submitLabel(.done)
            .lineLimit(1)
            .typographyV1(.headline4.size { _ in 18.0 }.lineHeight(30.0))
            .foregroundStyle(nameFieldColor)
            .tint(Color.SemanticV2.accentBrand)
            .contentShape(.rect)
            .onTapGesture {
                focusedField = .name
            }
            .onSubmit {
                focusedField = nil
            }
            .onChange(of: name) { _, _ in
                nameFieldColor = name.isEmpty ? Color.SemanticV2.foregroundInactive : Color.SemanticV2.foregroundPrimary
            }
            .overlay(alignment: .leading) {
                if focusedField != .name {
                    Image.Icon.editForm
                        .resizable()
                        .frame(width: 14, height: 14)
                        .foregroundStyle(Color.SemanticV2.foregroundInactive)
                        .offset(x: textWidth + 4)
                        .transition(.scale(scale: 0.9).combined(with: .opacity))
                }
            }
            .padding(.horizontal, 20)
            .padding(.bottom, 10)
    }

    @ViewBuilder
    private var captionSection: some View {
        VStack(spacing: 0) {
            TextField("\(L10n.FeatureManageClip.captionPlaceholder)...", text: $caption, axis: .vertical)
                .focused($focusedField, equals: .caption)
                .submitLabel(.return)
                .keyboardType(.twitter)
                .typographyV1(.body1.size { _ in 14.0 }.lineHeight(20.0))
                .foregroundStyle(captionFieldColor)
                .multilineTextAlignment(.leading)
                .lineSpacing(1.25)
                .padding(.horizontal, 20)
                .frame(minHeight: 34, maxHeight: 100, alignment: .top)
                .padding(.bottom, 8)
                .padding(.top, 8)
                .tint(Color.SemanticV2.accentBrand)
                .contentShape(.rect)
                .onTapGesture {
                    focusedField = .caption
                }
                .onChange(of: caption) { oldValue, newValue in
                    captionFieldColor = caption.isEmpty ? Color.SemanticV2.foregroundInactive : Color.SemanticV2.foregroundPrimary
                    if oldValue != newValue && newValue != store.captionField.currentValue {
                        store.send(.onCaptionTextUpdated(newValue))
                    }
                }

            userMentionDropdown

            HStack {
                Spacer()
                captionCharCount
            }
        }
    }

    @ViewBuilder
    private var userMentionDropdown: some View {
        if !store.userMentionSearchSuggestions.isEmpty {
            userMentionSearchList
                .id(ScrollTarget.userMentionDropdown.rawValue)
        }
    }

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

                    userMentionSearchListItem(user, showDivider: isMiddleItem)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal, 16)
                        .contentShape(Rectangle())
                        .onTapGesture {
                            store.send(.onUserMentionSearchItemTapped(user))
                        }
                        .simultaneousGesture(DragGesture(minimumDistance: 0).onChanged { _ in })
                }
            }
            .frame(maxWidth: .infinity)
        }
        .frame(height: min(CGFloat(store.userMentionSearchSuggestions.count) * 52, 200))
        .background(Color.SemanticV1.backgroundPrimary)
        .cornerRadius(8)
        .padding(.horizontal, 20)
        .padding(.bottom, 12)
    }

    private 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(Color.SemanticV1.textPrimary)
                            .lineLimit(1)
                    }
                    Text("@" + user.handle)
                        .typographyV1(.caption)
                        .foregroundColor(Color.SemanticV1.textTertiary)
                        .lineLimit(1)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
            }
            .contentShape(.rect)
            .padding(.vertical, 10)

            if showDivider {
                Divider()
                    .background(Color.SemanticV1.textTertiary)
            }
        }
    }

    private var header: some View {
        VStack(spacing: 0) {
            Button {
                store.send(.replaceSongArtTapped)
            } label: {
                Group {
                    if let videoCoverUrl = store.clip.videoCoverUrl, let playableUrl = URL(string: videoCoverUrl) {
                        ZStack {
                            RemoteImage(url: store.clip.largeImageUrl, fallbackId: store.clip.id.remoteId)
                                .aspectRatio(contentMode: .fill)
                            LoopingVideoPlayer(playableUrl: playableUrl, restartBeforePlaying: false, autoPlay: true, isPlaying: .constant(true), overrideTime: nil, fallbackView: {
                                coverImage
                            })
                        }
                        .id(store.coverArtRefreshId)
                        .frame(width: coverSize.width, height: coverSize.height)
                        .clipShape(RoundedRectangle(cornerRadius: 14))
                    } else {
                        coverImage
                    }
                }
                .overlay {
                    // previewButton  TODO: Add back once ready
                    editCoverIcon
                }
                .contentShape(.rect)
            }
        }
        .padding(.bottom, 30)
        .padding(.top, 2)
    }

    @ViewBuilder
    private var editCoverIcon: some View {
        Text(L10n.FeatureManageClip.editSongArt)
            .typographyV1(.subtitleSmall)
            .padding(.horizontal, 11)
            .padding(.vertical, 6)
            .foregroundStyle(Color.SemanticV2.foregroundPrimary)
            .background(Capsule().fill(.thinMaterial))
            .padding(.bottom, 10)
            .fixedSize(horizontal: false, vertical: true)
            .multilineTextAlignment(.center)
            .environment(\.colorScheme, .dark)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
            .frame(width: coverSize.width, height: coverSize.height)
            .background(.clear)
    }

    @ViewBuilder
    private var previewButton: some View {
        Text(L10n.FeatureManageClip.preview)
            .typographyV1(.subtitleSmall)
            .shadow(color: Color.SemanticV2.backgroundPrimary, radius: 2)
            .foregroundStyle(Color.SemanticV2.foregroundPrimary)
            .padding(.leading, 12)
            .padding(.top, 10)
            .environment(\.colorScheme, .dark)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
            .frame(width: coverSize.width, height: coverSize.height)
            .background(.clear)
    }

    private var coverImage: some View {
        RemoteImage(url: store.clip.largeImageUrl, fallbackId: store.clip.id.remoteId)
            .aspectRatio(contentMode: .fill)
            .frame(width: coverSize.width, height: coverSize.height)
            .clipShape(RoundedRectangle(cornerRadius: 14))
    }

    @ViewBuilder
    private var lyricsButton: some View {
        Button {
            store.send(.editLyricsTapped)
        } label: {
            editDisplayedLyricsRow
        }
        Divider()
    }

    @ViewBuilder
    private var displayStylesButton: some View {
        Button {
            store.send(.editDisplayStylesTapped)
        } label: {
            editDisplayStylesRow
        }
        Divider()
    }

    @ViewBuilder
    private var optionsButton: some View {
        Button {
            store.send(.moreOptionsTapped)
        } label: {
            moreOptionsRow
        }
        Divider()
    }

    @ViewBuilder
    private var editDisplayedLyricsRow: some View {
        HStack(spacing: 12) {
            Image.Icon.lyrics
                .resizable()
                .frame(width: 24, height: 24)
                .foregroundStyle(Color.SemanticV2.foregroundPrimary)
            Text(L10n.FeatureManageClip.editDisplayedLyrics)
                .foregroundStyle(Color.SemanticV2.foregroundPrimary)
                .typographyV1(.body1.lineHeight(24))
            Spacer()
            Image.Icon.chevronDown
                .resizable()
                .frame(width: 18, height: 18)
                .rotationEffect(Angle(degrees: -90.0))
                .foregroundStyle(Color.SemanticV2.foregroundInactive)
        }
        .padding(.vertical, 16)
    }

    @ViewBuilder
    private var editDisplayStylesRow: some View {
        HStack(spacing: 12) {
            Image.Icon.musicNote
                .renderingMode(.template)
                .frame(width: 24, height: 24)
                .foregroundStyle(Color.SemanticV2.foregroundPrimary)

            VStack {
                HStack(spacing: 4) {
                    Text(L10n.FeatureManageClip.editStyleSummary)
                        .foregroundStyle(Color.SemanticV2.foregroundPrimary)
                        .typographyV1(.body1.lineHeight(0))
                        .lineLimit(1)

                    /// `?` icon
                    Button {
                        store.send(.editStyleSummaryTooltipTapped)
                    } label: {
                        Image.Icon.helpFilled
                            .resizable()
                            .frame(width: 16, height: 16)
                            .contentShape(.rect)
                            .foregroundStyle(Color.SemanticV1.iconPrimary)
                    }
                    .buttonStyle(.plain)
                    .popover(isPresented: $store.showStyleSummaryTooltip) {
                        Text(L10n.FeatureManageClip.displayStylePopoverText)
                            .typographyV1(.caption)
                            .foregroundStyle(Color.SemanticV1.textPrimary)
                            .padding()
                            .presentationCompactAdaptation(.popover)
                    }

                    Spacer()
                }

                if let displayTags = store.clip.displayTags, !displayTags.isEmpty {
                    Text(displayTags)
                        .foregroundStyle(Color.SemanticV2.foregroundInactive)
                        .typographyV1(.caption5.lineHeight(0))
                        .lineLimit(1)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
            }
            Spacer()
            Image.Icon.chevronDown
                .resizable()
                .frame(width: 18, height: 18)
                .rotationEffect(Angle(degrees: -90.0))
                .foregroundStyle(Color.SemanticV2.foregroundInactive)
        }
        .padding(.vertical, 16)
    }

    @ViewBuilder
    private var moreOptionsRow: some View {
        HStack(spacing: 12) {
            Image.Icon.cogV2
                .resizable()
                .frame(width: 24, height: 24)
                .foregroundStyle(Color.SemanticV2.foregroundPrimary)
            Text(L10n.FeatureManageClip.moreOptions)
                .foregroundStyle(Color.SemanticV2.foregroundPrimary)
                .typographyV1(.body1.lineHeight(24))
            Spacer()
            Image.Icon.chevronDown
                .resizable()
                .frame(width: 18, height: 18)
                .rotationEffect(Angle(degrees: -90.0))
                .foregroundStyle(Color.SemanticV2.foregroundInactive)
        }
        .padding(.vertical, 16)
    }

    @ViewBuilder
    private func remixOriginRow(parentClip: ParentClip) -> some View {
        HStack(spacing: 12) {
            /// Song Image
            RemoteImage(url: parentClip.imageUrl, fallbackId: parentClip.id.remoteId)
                .clipShape(.rect(cornerRadius: 4))
                .frame(width: 24, height: 30)
                .background(Color.SemanticV2.backgroundTertiary)

            /// Row title + Song Info
            VStack {
                HStack(spacing: 4) {
                    /// `Show Remix Origin` or `Remix Origin`
                    Text(store.canToggleShowRemixOrigin ? L10n.FeatureManageClip.showRemixOrigin : L10n.FeatureManageClip.remixOrigin)
                        .foregroundStyle(Color.SemanticV2.foregroundPrimary)
                        .typographyV1(.body1.lineHeight(0))
                        .lineLimit(1)

                    /// `?` icon
                    Button {
                        store.send(.remixOriginTooltipTapped)
                    } label: {
                        Image.Icon.helpFilled
                            .resizable()
                            .frame(width: 16, height: 16)
                            .contentShape(.rect)
                    }
                    .buttonStyle(.plain)
                    .popover(isPresented: $store.showRemixOriginTooltip) {
                        Text(store.canToggleShowRemixOrigin ? L10n.FeatureManageClip.showRemixOriginPopoverText : L10n.FeatureManageClip.remixOriginPopoverText)
                            .typographyV1(.caption)
                            .padding()
                            .presentationCompactAdaptation(.popover)
                    }

                    Spacer()
                }

                /// `Song Title - @userHandle``
                Text("\(parentClip.title) — \(parentClip.userDisplayName ?? parentClip.userHandle)")
                    .foregroundStyle(Color.SemanticV2.foregroundInactive)
                    .typographyV1(.caption5.lineHeight(0))
                    .lineLimit(1)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }

            if case .updating = $store.showRemixOriginToggle.wrappedValue {
                ProgressView()
                    .frame(width: 24, height: 24)
                    .foregroundStyle(Color.SemanticV2.foregroundSecondary)
                    .tint(Color.SemanticV2.accentBrand)
            }

            Spacer()

            /// Only show toggle when allowed to
            if store.canToggleShowRemixOrigin {
                Toggle(isOn: Binding(get: { $store.showRemixOriginToggle.wrappedValue.currentValue }, set: { newValue in
                    store.send(.showRemixOriginToggleTapped(newValue))
                })) {}
                    .tint(Color.SemanticV2.accentBrand)
                    .labelsHidden()
            }
        }
        .padding(.vertical, 16)

        Divider()
    }

    @ViewBuilder
    private var captionCharCount: some View {
        Text(verbatim: "\(caption.count)/\(captionCharacterLimit)")
            .typographyV1(.caption4)
            .monospacedDigit()
            .foregroundStyle(Color.SemanticV2.foregroundInactive)
            .padding(.trailing, 20)
            .padding(.bottom, 12)
    }

    @ViewBuilder
    private func submitButton(label: String) -> some View {
        Button {
            Task {
                try? await Task.sleep(for: .milliseconds(100))
                store.send(.updateFields(name, caption, styleSummary))
                store.send(.publishTapped)
            }
        } label: {
            Text(label)
                .typographyV1(.body1.lineHeight(24))
                .foregroundStyle(Color.SemanticV2.backgroundPrimary)
                .opacity(store.isPublishing ? 0 : 1)
                .padding(.vertical, 14)
                .frame(maxWidth: .infinity)
                .background(RoundedRectangle(cornerRadius: 40).fill(Color.SemanticV2.foregroundPrimary).shadow(color: Color.SemanticV2.backgroundPrimary, radius: 3))
                .contentShape(.rect)
                .overlay {
                    ProgressView()
                        .frame(width: 24, height: 24)
                        .tint(Color.SemanticV2.backgroundPrimary)
                        .opacity(store.isPublishing ? 1 : 0)
                }
        }
        .buttonStyle(ScaleButtonStyle(scaleAmount: 0.98))
        .padding(.bottom, 14)
        .padding(.horizontal, 20)
    }

    @ViewBuilder
    private var dismissButton: some View {
        ToolbarButton(.close, color: Color.SemanticV2.foregroundPrimary, background: Color.SemanticV1.backgroundQuaternary) {
            store.send(.updateFields(name, caption, styleSummary))
            store.send(.dismissTapped)
        }
    }

    @ViewBuilder
    private var navigationTitle: some View {
        Text(Mode.navigationTitle)
            .typographyV1(.headline4.size { _ in 16.0 }.lineHeight(20.0))
            .foregroundStyle(Color.SemanticV2.foregroundPrimary)
    }

    @ViewBuilder
    private var saveButton: some View {
        let color = hasUnsavedChanges ? Color.SemanticV2.backgroundPrimary : Color.SemanticV2.foregroundInactive
        ToolbarButton(store.isSaving ? .loading : .checkmark,
            color: color,
            background: Color.SemanticV2.foregroundPrimary,
            prominent: hasUnsavedChanges
        ) {
            guard !store.isSaving, hasUnsavedChanges else { return }
            store.send(.updateFields(name, caption, styleSummary))
            store.send(.saveTapped)
            focusedField = nil
        }
        .disabled(!hasUnsavedChanges || store.isSaving)
    }

    private let coverSize: CGSize = .init(width: 103, height: 184)

    private var hasUnsavedChanges: Bool {
        return name != store.nameField.initialValue || caption != store.captionField.initialValue || styleSummary != store.styleSummaryField.initialValue
    }
}

// Type aliases for convenience
public typealias EditSongDetailsView = SongDetailsView<EditSongDetailsMode>
public typealias PublishSongView = SongDetailsView<PublishSongMode>
