import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureClipList
import FeaturePlayer
import FeaturePlaylistDetail
import FeatureProfile
import FeatureToasts
import Localization
import NavigationRouterClient
import StatsigClient
import SwiftUI
import Utilities

@Reducer
public struct Search {
    @Reducer(state: .equatable)
    public enum Destination {
        case profile(PublicProfileV1)
        case playlistDetail(PlaylistDetail)
    }

    @ObservableState
    public struct State: Equatable {
        enum Field: String, Hashable {
            case search
        }

        struct Search: Equatable {
            var rank: SearchRank = .trending
            var term: String = ""
        }

        var focusedField: Field? = .search
        var term: String = ""
        @ObservationStateIgnored @ObservedBox var search: Search = .init()
        @ObservationStateIgnored @ObservedBox var clipList: ClipList.State
        @ObservationStateIgnored @ObservedBox var profileList: ProfileList.State
        @ObservationStateIgnored @ObservedBox var playlistList: PlaylistList.State

        @Shared var me: Me
        public var searchType: SearchType
        public var lastSearchedType: SearchType?
        var isSearching = false
        @Shared(.inMemory(.isCompactPlayerVisible)) fileprivate var isCompactPlayerVisible = false

        @Presents public var destination: Destination.State?

        public var hasResults: Bool {
            switch searchType {
            case .playlist: !playlistList.playlists.isEmpty
            case .user: !profileList.profiles.isEmpty
            default: !clipList.clips.isEmpty
            }
        }

        var title: String {
            guard term.isEmpty else { return L10n.FeatureSearch.topResults }
            switch searchType {
            case .librarySong: return L10n.FeatureSearch.library
            case .publicSong: return L10n.FeatureSearch.popularSongs
            case .tagSong: return L10n.FeatureSearch.popularSongs
            case .playlist: return L10n.FeatureSearch.popularPlaylists
            case .user: return L10n.FeatureSearch.popularUsers
            }
        }

        public init(me: Shared<Me>, searchType: SearchType) {
            self._me = me
            self.searchType = searchType
            self.clipList = .init(me: me, context: SessionContext(source: .search))
            self.profileList = .init()
            self.playlistList = .init()

            self.clipList.pages.nextPage = .count
            self.profileList.pages.nextPage = .count
            self.playlistList.pages.nextPage = .count
        }
    }

    public enum Action: BindableAction {
        public enum Delegate {
            case playClipsAt(Clip, [Clip])
            case playlistTapped(Playlist)
            case authorTapped(String)
        }

        case onAppear
        case focusOnSearchField
        case updateSearchType(SearchType)
        case dismissTapped
        case search(String, SearchType)
        case playClip(ClipListItem.State)

        case delegate(Delegate)
        case clipList(ClipList.Action)
        case binding(BindingAction<State>)
        case profileList(ProfileList.Action)
        case playlistList(PlaylistList.Action)

        case destination(PresentationAction<Destination.Action>)
    }

    @Dependency(\.dismiss) var dismiss
    @Dependency(\.apiClientV2) var api
    @Dependency(\.mainQueue) var mainQueue
    @Dependency(NavigationRouterClient.self) private var navigationRouter
    @Dependency(\.toastClient.show) private var showToast

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.clipList, action: \.clipList) {
            ClipList()
        }
        .withClipListClient { state in
            .init(getClips: { page in
                do {
                    let result = try await api.search(page, state.searchType, state.search.term, state.search.rank)
                    return result.clips
                } catch {
                    return []
                }
            })
        }
        Scope(state: \.profileList, action: \.profileList) {
            ProfileList()
        }
        .withProfileListClient { state in
            .init(getProfiles: { page in
                do {
                    let result = try await api.search(page, state.searchType, state.search.term, state.search.rank)
                    return result.users
                }
            })
        }
        Scope(state: \.playlistList, action: \.playlistList) {
            PlaylistList()
        }
        .withPlaylistListClient { state in
            .init(getPlaylists: { page in
                do {
                    let result = try await api.search(page, state.searchType, state.search.term, state.search.rank)
                    return result.playlists
                }
            })
        }
        BindingReducer()
        Reduce<State, Action> { state, action in
            struct SearchDebounceId: Hashable {}

            switch action {
            case .onAppear:
                return .send(.clipList(.loadClips))

            case .dismissTapped:
                state.focusedField = nil
                return .run { _ in
                    await dismiss()
                }

            case .focusOnSearchField:
                state.focusedField = .search
                return .none

            case .binding(\.term):
                return .run { [term = state.term, type = state.searchType] send in
                    try await withTaskCancellation(id: SearchDebounceId(), cancelInFlight: true) {
                        try await Task.sleep(for: .seconds(term.isEmpty ? 0.0 : 0.5))
                        await send(.search(term, type))
                    }
                }

            case .updateSearchType(let type):
                state.searchType = type
                return .run { [term = state.term] send in
                    await send(.search(term, type))
                }

            case .search(let term, let type):
                guard state.focusedField == .search || type != state.lastSearchedType else { return .none }

                state.lastSearchedType = type
                state.isSearching = true
                state.search = .init(rank: term.isEmpty ? .trending : .relevant, term: term)

                switch state.searchType {
                case .playlist:
                    return .send(.playlistList(.loadPlaylists))

                case .user:
                    return .send(.profileList(.loadProfiles))

                default:
                    return .send(.clipList(.loadClips))
                }

            case .playClip(let clipItem):
                state.focusedField = nil
                return .send(.delegate(.playClipsAt(clipItem.clip, state.clipList.clips.map(\.clip))))

            case .clipList(.internal(.clipsLoadResult)),
                 .profileList(.internal(.profilesLoadResult)),
                 .playlistList(.internal(.playlistsLoadResult)):
                state.isSearching = false
                return .none

            case .clipList(.delegate(.toastAfter(let toast))):
                showToast(toast)
                return .none

            case .profileList(.profiles(.element(_, action: .authorTapped(let handle)))):
                navigationRouter.sendIfNavV2(route: .profile(handle), else: {
                    state.destination = .profile(.init(me: state.$me, handle: handle))
                })
                return .none

            case .playlistList(.playlists(.element(_, action: .playlistTapped(let playlist)))):
                navigationRouter.sendIfNavV2(route: .playlist(playlist), else: {
                    state.destination = .playlistDetail(.init(me: state.$me, source: .playlist(playlist)))
                })
                return .none

            case .binding, .clipList, .delegate, .profileList, .playlistList, .destination:
                // Catch-all
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct SearchScreen: View {
    @Bindable private var store: StoreOf<Search>
    @FocusState private var focusedField: Search.State.Field?
    @Namespace private var namespace

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

    public var body: some View {
        content
            .safeAreaInset(edge: .top) {
                toolbar
                    .background(gradientBackground)
            }
    }

    public var gradientBackground: some View {
        LinearGradient
            .eased(startColor: Color.SemanticV1.backgroundPrimary,
                   endColor: Color.SemanticV1.backgroundPrimary.opacity(0),
                   stops: 16,
                   easing: { t in
                       t * t * (3 - 2 * t)
                   })
            .ignoresSafeArea()
    }

    public var content: some View {
        List {
            Section(header: header) {
                if !store.isSearching, !store.term.isEmpty, !store.hasResults {
                    NoResultsView(message: L10n.FeatureSearch.noResults(store.term.lowercased()))
                        .padding(.top, UIScreen.height / 4.0)
                } else {
                    switch store.state.searchType {
                    case .user:
                        ProfileListContent(store: store.scope(state: \.profileList, action: \.profileList)) { _, item in
                            item
                        }

                    case .playlist:
                        PlaylistListContent(store: store.scope(state: \.playlistList, action: \.playlistList)) { _, item in
                            item
                        }

                    default:
                        ClipListContent(store: store.scope(state: \.clipList, action: \.clipList)) { _, item in
                            item
                        }
                    }
                }
            }
            .listRowBackground(Color.clear)
            .listSectionSeparator(.hidden)
            .listRowSeparator(.hidden)
            .listSectionSpacing(.zero)
            .listRowInsets(.init())
            .textCase(nil)
        }
        .scrollIndicators(.hidden)
        .scrollDismissesKeyboard(.immediately)
        .scrollBounceBehavior(.basedOnSize)
        .scrollContentBackground(.hidden)
        .listStyle(.grouped)
        .contentMargins(.horizontal, 16, for: .scrollContent)
        .omniPlayerSafeArea(includeTabBar: true)
        .fullScreenCover(item: $store.scope(state: \.destination?.profile, action: \.destination.profile)) { store in
            NavigationStack {
                PublicProfileScreenV1(store: store)
                    .toolbar {
                        ToolbarItem(placement: .navigationBarTrailing) {
                            ToolbarButton(.close, background: Color.SemanticV1.backgroundQuaternary) {
                                store.send(.dismiss)
                            }
                        }
                    }
            }
        }
        .fullScreenCover(item: $store.scope(state: \.destination?.playlistDetail, action: \.destination.playlistDetail)) { store in
            NavigationStack {
                PlaylistDetailScreen(store: store)
                    .toolbar {
                        ToolbarItem(placement: .topBarLeading) {
                            ToolbarButton(.close, background: Material.ultraThin, colorScheme: store.averageColors.colorScheme) { store.send(.dismiss) }
                        }
                    }
            }
        }
        .onAppear { store.send(.onAppear) }
        .bind($store.focusedField, to: $focusedField)
        .background(Color.SemanticV1.backgroundPrimary)
    }

    @ViewBuilder
    private var toolbar: some View {
        VStack {
            HStack {
                searchBar
                    .glassBackground(shape: .capsule, fallbackStyle: .ultraThinMaterial, interactive: true)
                    .contentShape(.rect)
                    .onTapGesture {
                        store.send(.focusOnSearchField)
                    }

                ToolbarButton(.close, background: Material.ultraThinMaterial, glass: true, action: {
                    store.send(.dismissTapped)
                })
                .frame(width: 54, height: 54)
            }

            picker
        }
        .glassEffectContainer()
        .padding(.horizontal, 16)
        .padding(.bottom, 8)
        .padding(.top, 4)
    }

    private var searchBar: some View {
        HStack(spacing: 8) {
            Image.Icon.search
                .foregroundColor(Color.SemanticV1.iconPrimary)

            TextField(text: $store.term) {
                Text(L10n.FeatureSearch.searchPlaceholder)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            .autocorrectionDisabled()
            .typographyV1(.body1)
            .focused($focusedField, equals: .search)
            .foregroundColor(Color.SemanticV1.textPrimary)
        }
        .padding(.horizontal, 12)
        .frame(height: 48)
    }

    @ViewBuilder
    private var picker: some View {
        if #available(iOS 26.0, *) {
            Picker("Search Type", selection: Binding(
                get: { store.searchType },
                set: { store.send(.updateSearchType($0)) }
            )) {
                Text(L10n.FeatureSearch.songs).tag(SearchType.publicSong)
                Text(L10n.FeatureSearch.users).tag(SearchType.user)
                Text(L10n.FeatureSearch.playlists).tag(SearchType.playlist)
            }
            .frame(height: 34)
            .typographyV1(.button)
            .pickerStyle(.segmented)
            .saturation(0) // Removes iOS blue tint
            .padding(.bottom, 1) // Optical alignment
            .glassEffect(.regular.interactive(), in: .capsule)
        } else {
            HStack(spacing: 8) {
                searchTypeButton(title: L10n.FeatureSearch.songs, highlighted: store.searchType == .publicSong) {
                    store.send(.updateSearchType(.publicSong))
                }
                searchTypeButton(title: L10n.FeatureSearch.users, highlighted: store.searchType == .user) {
                    store.send(.updateSearchType(.user))
                }
                searchTypeButton(title: L10n.FeatureSearch.playlists, highlighted: store.searchType == .playlist) {
                    store.send(.updateSearchType(.playlist))
                }
            }
        }
    }

    private var header: some View {
        HStack {
            Text(store.title)
                .typographyV1(.headline3)
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .frame(maxWidth: .infinity, alignment: .leading)

            if store.isSearching {
                GradientSpinner(size: .large)
                    .transition(.blurReplace)
            }
        }
        .animation(.snappy(duration: 0.25), value: store.isSearching)
        .padding(.vertical, 12)
    }

    @ViewBuilder
    func searchTypeButton(title: String, highlighted: Bool, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Text(title)
                .typographyV1(.body2.neueMontrealMedium())
                .frame(maxWidth: .infinity)
                .padding(.vertical, 6)
                .foregroundColor(highlighted ? Color.SemanticV1.textInvert : Color.SemanticV1.textPrimary)
                .background(highlighted ? Color.SemanticV1.backgroundInvert.opacity(0.85) : .clear, in: .rect(cornerRadius: 8))
                .background(.ultraThinMaterial, in: .rect(cornerRadius: 8))
                .contentShape(.rect)
        }
        .zIndex(highlighted ? 2 : 1)
    }
}
