import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureClipList
import FeatureManagePlaylist
import FeaturePlaylistDetail
import Localization
import NavigationRouterClient
import StatsigClient
import SwiftUI
import Utilities

@Reducer
public struct Playlists {
    @Reducer(state: .equatable)
    public enum Destination {
        case playlistDetail(PlaylistDetail)
        case createPlaylist(CreatePlaylist)
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?
        @ObservationStateIgnored @ObservedBox var playlistList: PlaylistList.State = .init(firstPageIndex: 1)

        @Shared var me: Me
        var loadState: LoadState { playlistList.loadState }
        let showBackButton: Bool

        public init(me: Shared<Me>, showBackButton: Bool = true) {
            self._me = me
            self.showBackButton = showBackButton
        }
    }

    public enum Action {
        case destination(PresentationAction<Destination.Action>)
        case playlistList(PlaylistList.Action)

        case task
        case dismiss
        case updateClip(Clip)
        case deleteClip(Clip)
        case createTapped
        case `internal`(Internal)

        public enum Internal {
            case playlistArtworkUpdated(Playlist)
        }
    }

    @Dependency(\.apiClientV2) var api
    @Dependency(\.dismiss) var dismiss
    @Dependency(NavigationRouterClient.self) private var navigationRouter
    @Dependency(\.continuousClock) var clock

    struct PlaylistAssetArtCancellableId: Hashable {
        let playlistId: String
    }

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.playlistList, action: \.playlistList) {
            PlaylistList()
        }
        .withPlaylistListClient { _ in
            .init(getPlaylists: { page in
                do {
                    let result = try await api.getPlaylists(page: page)
                    return result.playlists
                }
            })
        }
        Reduce<State, Action> { state, action in
            switch action {
            case .task:
                return .send(.playlistList(.loadPlaylists))

            case .createTapped:
                state.destination = .createPlaylist(.init(shouldAddToPlaylistAfterCreating: false))
                return .none

            case .updateClip(let clip):
                switch state.destination {
                case .playlistDetail:
                    return .send(.destination(.presented(.playlistDetail(.updateClip(clip)))))
                case .createPlaylist, .none:
                    return .none
                }

            case .deleteClip(let clip):
                switch state.destination {
                case .playlistDetail:
                    return .send(.destination(.presented(.playlistDetail(.deleteClip(clip)))))
                case .createPlaylist, .none:
                    return .none
                }

            case .dismiss:
                let effects = state.playlistList.playlists.map { playlist in
                    Effect<Action>.cancel(id: PlaylistAssetArtCancellableId(playlistId: playlist.playlist.id))
                }
                return .merge(
                    effects + [.run { _ in await dismiss() }]
                )

            case .destination(.presented(.createPlaylist(.delegate(.savedPlaylist(let playlist))))):
                if let index = state.playlistList.playlists.firstIndex(where: { $0.playlist.id == playlist.id }) {
                    state.playlistList.playlists[index].playlist = playlist
                } else {
                    state.playlistList.playlists.insert(.init(playlist: playlist, position: 0), at: 0)
                }

                return .run { send in
                    do {
                        for await _ in clock.timer(interval: .seconds(3)).prefix(4) {
                            if Task.isCancelled { return }
                            var updatedPlaylist = try await api.getPlaylistById(0, playlist.id, nil, nil)
                            if updatedPlaylist.imageUrl != nil {
                                updatedPlaylist.userHandle = nil
                                updatedPlaylist.userDisplayName = nil
                                await send(.internal(.playlistArtworkUpdated(updatedPlaylist)))
                                break
                            }
                        }
                    } catch {
                        log.telemetry.error(error, message: "Failed to fetch playlist artwork")
                    }
                }
                .cancellable(id: PlaylistAssetArtCancellableId(playlistId: playlist.id), cancelInFlight: true)

            case .internal(.playlistArtworkUpdated(let playlist)):
                if let index = state.playlistList.playlists.firstIndex(where: { $0.playlist.id == playlist.id }) {
                    state.playlistList.playlists[index].playlist = playlist
                }
                return .none

            case .playlistList(.playlists(.element(_, action: .playlistTapped(let playlist)))):
                navigationRouter.send(route: .playlist(playlist))
                return .none

            case .playlistList,
                 .destination,
                 .internal:
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct PlaylistsScreen: View {
    @Bindable var store: StoreOf<Playlists>

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

    public var body: some View {
        Group {
            switch store.loadState {
            case .loading:
                LoadingView()

            case .loaded:
                loadedView

            case .failed(let message):
                FailedView(
                    title: L10n.FeatureCatalog.errorTitle,
                    message: message,
                    buttonTitle: L10n.FeatureCatalog.retry,
                    action: { store.send(.task) }
                )
            }
        }
        .modifier(if: store.showBackButton) {
            $0.customBackButton(background: Color.SemanticV1.backgroundQuaternary, action: { store.send(.dismiss) })
        }
        .navigationDestination(item: $store.scope(state: \.destination?.playlistDetail, action: \.destination.playlistDetail)) { store in
            PlaylistDetailScreen(store: store)
                .toolbar {
                    ToolbarItem(placement: .topBarLeading) {
                        ToolbarButton(.back, background: Material.ultraThin, colorScheme: store.averageColors.colorScheme) {
                            store.send(.dismiss)
                        }
                    }
                }
        }
        .sheet(item: $store.scope(state: \.destination?.createPlaylist, action: \.destination.createPlaylist)) { store in
            CreatePlaylistScreen(store: store)
                .presentationDetents([.height(720)])
                .presentationDragIndicator(.hidden)
        }
        .navigationTitle(Text(L10n.FeatureCatalog.playlists))
        .navigationBarTitleDisplayMode(.large)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                ToolbarButton(.create, background: Color.SemanticV1.backgroundQuaternary, action: {
                    store.send(.createTapped)
                })
            }
        }
        .task {
            store.send(.task)
        }
    }

    public var loadedView: some View {
        List {
            Section {
                PlaylistListContent(store: store.scope(state: \.playlistList, action: \.playlistList)) { _, item in
                    item
                }
                .padding(.horizontal, 16.0)
            }
            .listRowBackground(Color.clear)
            .listSectionSeparator(.hidden, edges: .top)
            .listSectionSpacing(.zero)
            .listRowInsets(.init())
            .textCase(nil)
        }
        .scrollDismissesKeyboard(.immediately)
        .listStyle(.grouped)
        .scrollContentBackground(.hidden)
        .contentMargins(.horizontal, 12, for: .scrollContent)
        .background(Color.SemanticV1.backgroundPrimary)
        .stableRefreshable {
            await store.send(.task).finish()
        }
        .overlay {
            if store.playlistList.playlists.isEmpty {
                emptyView
            }
        }
    }

    private var emptyView: some View {
        VStack(spacing: 8) {
            Text(L10n.FeatureCatalog.emptyTitlePlaylists)
                .typographyV1(.headline4)
                .multilineTextAlignment(.center)
                .foregroundColor(Color.SemanticV1.textPrimary)

            Button {
                store.send(.createTapped)
            } label: {
                Text(L10n.FeatureCatalog.emptyMessagePlaylistsTap)
                    .foregroundStyle(Color.SemanticV1.textBrand)
                    .inlineTypographyV1(.body1) +
                    Text(" \(Image(systemName: "plus.circle.fill")) ")
                    .foregroundStyle(Color.SemanticV1.textBrand)
                    .inlineTypographyV1(.body1) +
                    Text(L10n.FeatureCatalog.emptyMessagePlaylistsGetStarted)
                    .foregroundStyle(Color.SemanticV1.textBrand)
                    .inlineTypographyV1(.body1)
            }
        }
        .padding(34)
        .listRowSeparator(.hidden)
    }
}
