import APIClient
import ComposableArchitecture
import FeatureToasts
import Foundation
import Localization

@Reducer
public struct OrpheusWorkspacesReducer {
    @ObservableState
    public struct State: Equatable {
        public var allWorkspaces: [Workspace] = []
        public var searchWorkspaces: [Workspace] = []
        public var searchQuery: String = ""
        public var selectedWorkspaceId: Workspace.ID?
        public var isFetchingWorkspaces = false
        public var currentPage = 0
        public var totalExpected: Int?
        public var fetchError: String?
        public var toast: ToastType?
        
        public var workspaces: [Workspace] {
            searchQuery.isEmpty ? allWorkspaces : searchWorkspaces
        }
        
        public var canFetchMore: Bool {
            guard searchQuery.isEmpty,
                  let totalExpected else {
                return false
            }
            return workspaces.count < totalExpected
        }
        
        public init() {}
    }
    
    public enum Action: Equatable {
        public enum Internal: Equatable {
            case trashWorkspaceResult(TaskResult<Workspace.ID>)
            case fetchWorkspacesResult(TaskResult<WorkspacesPage>)
            case setToast(ToastType?)
        }
        
        case `internal`(Internal)
        case onAppear
        case fetchWorkspaces(shouldPaginate: Bool)
        case workspaceSelected(workspaceId: Workspace.ID)
        case createWorkspace
        case trashWorkspace(workspaceId: Workspace.ID)
        case searchQueryChanged(String)
    }
    
    private enum CancelID {
        case fetchWorkspaces
        case debouncedSearch
    }
    
    @Dependency(\.workspacesClient) var workspacesClient
    
    public init() {}
    
    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                guard state.allWorkspaces.isEmpty && !state.isFetchingWorkspaces else {
                    return .none
                }
                return .send(.fetchWorkspaces(shouldPaginate: false))
                
            case let .fetchWorkspaces(shouldPaginate):
                if shouldPaginate {
                    guard !state.isFetchingWorkspaces else { return .none }
                    guard state.canFetchMore else { return .none }
                }
                
                state.isFetchingWorkspaces = true
                let targetPage = shouldPaginate ? state.currentPage + 1 : 0
                let query = state.searchQuery.isEmpty ? nil : state.searchQuery
                
                if !shouldPaginate {
                    state.currentPage = 0
                    state.fetchError = nil
                    state.totalExpected = nil
                }
                
                return .run { send in
                    await send(.internal(.fetchWorkspacesResult(
                        TaskResult { try await workspacesClient.getMyWorkspaces(targetPage, query) }
                    )))
                }
                .cancellable(id: CancelID.fetchWorkspaces, cancelInFlight: true)
                
            case let .internal(.fetchWorkspacesResult(.success(page))):
                let isFirstLoad = state.currentPage == 0
                let isSearching = !state.searchQuery.isEmpty
                
                state.isFetchingWorkspaces = false
                state.currentPage = page.currentPage
                state.totalExpected = page.totalResults
                
                if isSearching {
                    if isFirstLoad {
                        state.searchWorkspaces = page.workspaces
                    } else {
                        state.searchWorkspaces.append(contentsOf: page.workspaces)
                    }
                } else {
                    if isFirstLoad {
                        state.allWorkspaces = page.workspaces
                    } else {
                        state.allWorkspaces.append(contentsOf: page.workspaces)
                    }
                }
                return .none
                
            case .internal(.fetchWorkspacesResult(.failure(let error))):
                state.isFetchingWorkspaces = false
                state.fetchError = error.localizedDescription
                return .none
                
            case let .workspaceSelected(workspaceId):
                state.selectedWorkspaceId = workspaceId
                // TODO: Update Orpheus session with selected workspace
                return .none
                
            case .createWorkspace:
                // TODO: Implement workspace creation
                return .none
                
            case let .trashWorkspace(workspaceId):
                return .run { send in
                    await send(.internal(.trashWorkspaceResult(
                        TaskResult {
                            try await workspacesClient.trashWorkspace(workspaceId)
                            return workspaceId
                        }
                    )))
                }
                
            case let .internal(.trashWorkspaceResult(.success(workspaceId))):
                state.allWorkspaces.removeAll { $0.id == workspaceId }
                state.searchWorkspaces.removeAll { $0.id == workspaceId }
                if state.selectedWorkspaceId == workspaceId {
                    state.selectedWorkspaceId = nil // TODO: ensure this is expected behavior
                }
                return .none
                
            case .internal(.trashWorkspaceResult(.failure)):
                state.toast = .warning(
                    L10n.FeatureCreateClip.workspaceDeleteFailed,
                    position: .bottom
                )
                return .none
                
            case let .searchQueryChanged(query):
                guard query != state.searchQuery else {
                    return  .none
                }
                state.searchQuery = query
                state.searchWorkspaces = []
                
                guard !query.isEmpty else {
                    return .cancel(id: CancelID.debouncedSearch)
                }
                
                return .run { send in
                    try await Task.sleep(for: .milliseconds(300))
                    await send(.fetchWorkspaces(shouldPaginate: false))
                }
                .cancellable(id: CancelID.debouncedSearch, cancelInFlight: true)
                
            case .internal(.setToast(let toast)):
                state.toast = toast
                return .none
                
            case .internal:
                return .none
            }
        }
    }
}

