import AppCore
import Combine
import ComposableArchitecture
import ComposablePresentation
import Foundation
import HomeFeature
import RestoreFeature
import WelcomeFeature
import XXMessengerClient

struct AppState: Equatable {
  enum Screen: Equatable {
    case loading
    case welcome(WelcomeState)
    case restore(RestoreState)
    case home(HomeState)
    case failure(String)
  }

  @BindableState var screen: Screen = .loading
}

extension AppState.Screen {
  var asWelcome: WelcomeState? {
    get { (/AppState.Screen.welcome).extract(from: self) }
    set { if let newValue = newValue { self = .welcome(newValue) } }
  }
  var asRestore: RestoreState? {
    get { (/AppState.Screen.restore).extract(from: self) }
    set { if let state = newValue { self = .restore(state) } }
  }
  var asHome: HomeState? {
    get { (/AppState.Screen.home).extract(from: self) }
    set { if let newValue = newValue { self = .home(newValue) } }
  }
}

enum AppAction: Equatable, BindableAction {
  case start
  case binding(BindingAction<AppState>)
  case welcome(WelcomeAction)
  case restore(RestoreAction)
  case home(HomeAction)
}

struct AppEnvironment {
  var dbManager: DBManager
  var messenger: Messenger
  var mainQueue: AnySchedulerOf<DispatchQueue>
  var bgQueue: AnySchedulerOf<DispatchQueue>
  var welcome: () -> WelcomeEnvironment
  var restore: () -> RestoreEnvironment
  var home: () -> HomeEnvironment
}

extension AppEnvironment {
  static let unimplemented = AppEnvironment(
    dbManager: .unimplemented,
    messenger: .unimplemented,
    mainQueue: .unimplemented,
    bgQueue: .unimplemented,
    welcome: { .unimplemented },
    restore: { .unimplemented },
    home: { .unimplemented }
  )
}

let appReducer = Reducer<AppState, AppAction, AppEnvironment>
{ state, action, env in
  switch action {
  case .start, .welcome(.finished), .restore(.finished):
    state.screen = .loading
    return .run { subscriber in
      do {
        if env.dbManager.hasDB() == false {
          try env.dbManager.makeDB()
        }

        if env.messenger.isLoaded() == false {
          if env.messenger.isCreated() == false {
            subscriber.send(.set(\.$screen, .welcome(WelcomeState())))
            subscriber.send(completion: .finished)
            return AnyCancellable {}
          }
          try env.messenger.load()
        }

        subscriber.send(.set(\.$screen, .home(HomeState())))
      } catch {
        subscriber.send(.set(\.$screen, .failure(error.localizedDescription)))
      }
      subscriber.send(completion: .finished)
      return AnyCancellable {}
    }
    .subscribe(on: env.bgQueue)
    .receive(on: env.mainQueue)
    .eraseToEffect()

  case .welcome(.restoreTapped):
    state.screen = .restore(RestoreState())
    return .none

  case .welcome(.failed(let failure)):
    state.screen = .failure(failure)
    return .none

  case .binding(_), .welcome(_), .restore(_), .home(_):
    return .none
  }
}
.binding()
.presenting(
  welcomeReducer,
  state: .keyPath(\.screen.asWelcome),
  id: .notNil(),
  action: /AppAction.welcome,
  environment: { $0.welcome() }
)
.presenting(
  restoreReducer,
  state: .keyPath(\.screen.asRestore),
  id: .notNil(),
  action: /AppAction.restore,
  environment: { $0.restore() }
)
.presenting(
  homeReducer,
  state: .keyPath(\.screen.asHome),
  id: .notNil(),
  action: /AppAction.home,
  environment: { $0.home() }
)