import AnalyticsClient
import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureToasts
import Foundation
import Localization
import SwiftUI
import Utilities

@Reducer
public struct PlaylistList {
    @ObservableState
    public struct State: Equatable {
        public var playlists: IdentifiedArrayOf<PlaylistListItem.State> = []
        public var loadState: LoadState = .loading
        public var pages: PageState

        var firstLoad = true

        public init(firstPageIndex: Int = 1) {
            self.pages = .init(firstPageIndex: firstPageIndex)
        }

        public func playlists(startingAt playlist: PlaylistListItem.State) -> [PlaylistListItem.State] {
            guard let startIndex = playlists.index(id: playlist.id) else { return [] }
            return Array(playlists[startIndex...])
        }

        public func playlists(startingAt playlist: PlaylistListItem.State) -> [Playlist] {
            playlists(startingAt: playlist).map(\.playlist)
        }
    }

    public enum Action {
        public enum Internal {
            case playlistsLoadResult(page: Int, result: Result<[Playlist], Error>)
        }

        case `internal`(Internal)
        case playlists(IdentifiedActionOf<PlaylistListItem>)

        case loadPlaylists
        case getNextPage
    }

    @Dependency(PlaylistListClient.self) private var playlistListClient
    @Dependency(AnalyticsClient.self) private var analytics
    @Dependency(APIClient.self) var apiClient

    public init() {}

    public var body: some ReducerOf<Self> {
        Reduce<State, Action> { state, action in
            switch action {
            case .loadPlaylists:
                if state.firstLoad { state.loadState = .loading }
                state.firstLoad = false
                state.pages.reset()

                return .send(.getNextPage)

            case .getNextPage:
                guard let nextPage = state.pages.nextIndex() else { return .none }

                return .run { send in
                    await send(.internal(.playlistsLoadResult(page: nextPage, result: Result(catching: { try await playlistListClient.getPlaylists(nextPage) }))))
                }

            case .internal(.playlistsLoadResult(let page, .success(let playlists))):
                if page == state.pages.firstPageIndex {
                    state.playlists = []
                }

                let startIndex = state.playlists.count
                state.playlists.append(contentsOf: playlists.enumerated().map { index, playlist in
                    .init(playlist: playlist, position: (startIndex + index) + 1)
                })
                state.loadState = .loaded
                state.pages.update(playlists)

                return .none

            case .internal(.playlistsLoadResult(_, .failure(let error))):
                if state.playlists.isEmpty {
                    state.loadState = .failed(L10n.FeatureClipList.actionFailed)
                }
                state.pages.update(error)
                return .none

            case .playlists, .internal:
                // Catch-all
                return .none
            }
        }
        .forEach(\.playlists, action: \.playlists) {
            PlaylistListItem()
        }
    }
}

public struct PlaylistListContent<ItemView: View>: View {
    @Bindable private var store: StoreOf<PlaylistList>
    private var itemView: (StoreOf<PlaylistListItem>, PlaylistListItemView) -> ItemView

    public init(store: StoreOf<PlaylistList>) where ItemView == PlaylistListItemView {
        self.init(store: store, itemView: { $1 })
    }

    public init(store: StoreOf<PlaylistList>, @ViewBuilder itemView: @escaping (StoreOf<PlaylistListItem>, PlaylistListItemView) -> ItemView) {
        self.store = store
        self.itemView = itemView
    }

    public var body: some View {
        ForEach(store.scope(state: \.playlists, action: \.playlists)) { store in
            itemView(store, PlaylistListItemView(store: store))
        }

        if store.pages.hasMore, !store.playlists.isEmpty {
            ProgressView()
                .progressViewStyle(.circular)
                .padding()
                .frame(maxWidth: .infinity)
                .listRowSeparator(.hidden)
                .background(Color.clear)
                .listRowBackground(Color.clear)
                .id(UUID())
                .onAppear {
                    guard !store.pages.loadingNext else { return }
                    store.send(.getNextPage)
                }
        }
    }
}
