import APIClient
import ComponentLibrary
import ComposableArchitecture
import EventBusClient
import FeatureClipList
import FeatureToasts
import Localization
import SwiftUI
import Utilities

@Reducer
public struct Trending {
    @ObservableState
    public struct State: Equatable {
        var loadState: LoadState { clipList.loadState }
        @ObservationStateIgnored @ObservedBox var clipList: ClipList.State

        var selectedFilter: Filter?
        var selectedLanguage: Language?

        var filters: [Filter] = []
        var languages: [Language] = []

        let section: PlaylistSection
        @Shared var me: Me

        let showBackButton: Bool

        public init(
            section: PlaylistSection,
            me: Shared<Me>,
            showBackButton: Bool = true
        ) {
            self.section = section
            self._me = me
            self.clipList = .init(me: me, context: SessionContext(source: .trending(filters: [])))
            self.showBackButton = showBackButton
        }
    }

    public struct Language: Identifiable, Hashable, Equatable {
        public let id = UUID()
        let name: String

        var isGlobal: Bool {
            name.lowercased().contains("global")
        }
    }

    public struct Filter: Identifiable, Hashable, Equatable {
        public let id = UUID()
        let name: String
    }

    public enum Action {
        case clipList(ClipList.Action)

        case back
        case filterTapped(Filter)
        case languageTapped(Language)
        case getTrendingPeriod
        case task
        case trending
        case updateClip(Clip)
        case deleteClip(Clip)
    }

    @Dependency(\.apiClientV2) var api
    @Dependency(\.dismiss) var dismiss
    @Dependency(\.eventBus.getOmniplayerChannel) var getOmniplayerChannel
    @Dependency(\.toastClient.show) var showToast

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.clipList, action: \.clipList) {
            ClipList()
        }
        .withClipListClient { state in
            .init(getClips: { _ in
                let section = try await api.getTrendingPlaylist(
                    state.selectedLanguage?.name ?? "",
                    state.selectedFilter?.name ?? ""
                )
                return section.items
            })
        }

        Reduce<State, Action> { state, action in
            switch action {
            case .back:
                return .run { _ in await self.dismiss() }

            case .filterTapped(let filter):
                state.selectedFilter = filter
                return .send(.getTrendingPeriod)

            case .languageTapped(let language):
                state.selectedLanguage = language
                return .send(.getTrendingPeriod)

            case .getTrendingPeriod:
                return .send(.clipList(.loadClips))
                
            case .task:
                setSelectedFilterAndLanguageIfUnset(&state)
                return .send(.getTrendingPeriod)

            case .updateClip(let clip):
                return .send(.clipList(.updateClip(clip)))

            case .deleteClip(let clip):
                return .send(.clipList(.deleteClip(clip)))

            case .clipList:
                initializeFiltersAndLanguages(&state)
                setSelectedFilterAndLanguageIfUnset(&state)
                return .none

            case .trending:
                return .none
            }
        }
    }

    private func initializeFiltersAndLanguages(_ state: inout State) {
        if state.filters.isEmpty {
            state.filters = (state.section.secondaryOptions ?? []).map(Filter.init)
        }
        if state.languages.isEmpty {
            state.languages = (state.section.options ?? []).map(Language.init)
        }
    }

    private func setSelectedFilterAndLanguageIfUnset(_ state: inout State) {
        if state.selectedLanguage == nil,
           let language = state.languages.first(where: { $0.name == state.section.selectedOption })
        {
            state.selectedLanguage = language
        }
        if state.selectedFilter == nil,
           let filter = state.filters.first(where: { $0.name == state.section.secondarySelectedOption })
        {
            state.selectedFilter = filter
        }
        // Update the context to the selected language
        let filters: [ContextSource.TrendingFilter] = [.language(state.selectedLanguage?.name ?? ""), .period(state.selectedFilter?.name ?? "")]
        state.clipList.context = SessionContext(source: .trending(filters: filters))
    }
}

public struct TrendingScreen: View {
    @Bindable var store: StoreOf<Trending>
    @State private var headerVisible = true

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

    public var body: some View {
        loadedView
            .background(Color.SemanticV1.backgroundPrimary)
            .modifier(if: store.showBackButton) {
                $0.customBackButton(background: Color.SemanticV1.backgroundQuaternary) { store.send(.back) }
            }
            .toolbar {
                ToolbarItem(placement: .principal) {
                    ToolbarTitle(store.selectedFilter?.name ?? store.section.title, visibility: !headerVisible)
                }

                ToolbarItem(placement: .topBarTrailing) {
                    languagePicker
                }
            }
            .stableRefreshable {
                await store.send(.getTrendingPeriod).finish()
            }
            .task {
                store.send(.task)
            }
    }

    private var header: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text(L10n.FeatureDiscoverSectionDetail.trending)
                .typographyV1(.headline1)
                .foregroundStyle(Color.SemanticV1.textPrimary)

            if let description = store.section.description, !description.isEmpty {
                Text(description)
                    .typographyV1(.body1.neueMontrealRegular())
                    .foregroundStyle(Color.SemanticV1.textSecondary)
            }

            if !store.filters.isEmpty {
                filtersPicker
            }
        }
        .padding(.top, 32)
        .padding(.bottom, 32)
        .padding(.horizontal, 12)
        .onAppear { headerVisible = true }
        .onDisappear { headerVisible = false }
    }

    private var languagePicker: some View {
        Menu {
            ForEach(store.languages) { language in
                Toggle(isOn: Binding(
                    get: { store.selectedLanguage == language },
                    set: { isSelected in
                        if isSelected {
                            store.send(.languageTapped(language))
                        }
                    }
                )) {
                    Text(language.name)
                }
            }
        } label: {
            Image.Icon.globe
                .foregroundStyle(Color.SemanticV1.textPrimary)

            if let selectedLanguage = store.selectedLanguage, !selectedLanguage.isGlobal {
                Text(selectedLanguage.name)
                    .typographyV1(.body1)
                    .foregroundStyle(Color.SemanticV1.textPrimary)
            }
        }
    }

    private var filtersPicker: some View {
        Picker("", selection: .init(get: {
            store.selectedFilter
        }, set: { newValue in
            if let filter = newValue {
                store.send(.filterTapped(filter))
            }
        })) {
            ForEach(store.filters) { filter in
                Text(filter.name)
                    .tag(Optional(filter))
            }
        }
        .pickerStyle(.segmented)
        .controlSize(.regular)
        .blendMode(.luminosity)
    }

    private var loadedView: some View {
        List {
            Section(header: header) {
                switch store.loadState {
                case .loaded where store.clipList.clips.isEmpty:
                    NoResultsView(message: L10n.FeatureDiscoverSectionDetail.noResults(store.selectedFilter ?? "")) // TODO: gracefully handle?

                case .failed(let message):
                    FailedView(
                        title: L10n.FeatureDiscoverSectionDetail.errorTitle,
                        message: message,
                        buttonTitle: L10n.FeatureDiscoverSectionDetail.retry,
                        action: { store.send(.getTrendingPeriod) }
                    )

                default:
                    ClipListContent(store: store.scope(state: \.clipList, action: \.clipList)) { _, item in
                        item
                            .padding(.horizontal, 12)
                    }
                }
            }
            .listRowBackground(Color.clear)
            .listSectionSeparator(.hidden, edges: store.clipList.clips.isEmpty ? .all : .top)
            .listSectionSpacing(.zero)
            .listRowInsets(.init())
            .textCase(nil)
        }
        .listStyle(.grouped)
        .scrollContentBackground(.hidden)
    }
}

// MARK: - Previews

#if DEBUG
    #Preview {
        // TODO: (Sid) improve preview + simulate other cases
        let _ = prepareDependencies {
            $0[APIClientV2.self].getTrendingPlaylist = { @Sendable _, _ in
                PlaylistSection(
                    id: "test",
                    title: "Global Trending",
                    items: [Clip.mock(), Clip.mock(), Clip.mock()],
                    previewItemsCount: 0,
                    secondaryOptions: ["Weekly", "Now", "Monthly", "All Time"],
                    secondarySelectedOption: "Weekly"
                )
            }
        }

        return TrendingScreen(store: Store(
            initialState: Trending.State(
                section: PlaylistSection(id: "test", title: "Trending", items: [], previewItemsCount: 0),
                me: Shared(value: Me(models: [], roles: [:], flags: [:], user: User.mock()))
            ),
            reducer: { Trending() }
        ))
    }
#endif
