import Combine
import ComposableArchitecture
import ComposablePresentation
import LandingFeature
import SessionFeature
import XCTestDynamicOverlay

struct AppState: Equatable {
  enum Scene: Equatable {
    case landing(LandingState)
    case session(SessionState)
  }

  var id: UUID = UUID()
  var scene: Scene = .landing(LandingState(id: UUID()))
}

extension AppState.Scene {
  var asLanding: LandingState? {
    get {
      guard case .landing(let state) = self else { return nil }
      return state
    }
    set {
      guard let newValue = newValue else { return }
      self = .landing(newValue)
    }
  }

  var asSession: SessionState? {
    get {
      guard case .session(let state) = self else { return nil }
      return state
    }
    set {
      guard let newValue = newValue else { return }
      self = .session(newValue)
    }
  }
}

enum AppAction: Equatable {
  case viewDidLoad
  case cMixDidChange(hasCMix: Bool)
  case landing(LandingAction)
  case session(SessionAction)
}

struct AppEnvironment {
  var makeId: () -> UUID
  var hasCMix: () -> AnyPublisher<Bool, Never>
  var mainScheduler: AnySchedulerOf<DispatchQueue>
  var landing: LandingEnvironment
  var session: SessionEnvironment
}

let appReducer = Reducer<AppState, AppAction, AppEnvironment>
{ state, action, env in
  enum HasCMixEffectId {}

  switch action {
  case .viewDidLoad:
    return env.hasCMix()
      .removeDuplicates()
      .map(AppAction.cMixDidChange(hasCMix:))
      .receive(on: env.mainScheduler)
      .eraseToEffect()
      .cancellable(id: HasCMixEffectId.self, cancelInFlight: true)

  case .cMixDidChange(let hasClient):
    if hasClient {
      let sessionState = state.scene.asSession ?? SessionState(id: env.makeId())
      state.scene = .session(sessionState)
    } else {
      let landingState = state.scene.asLanding ?? LandingState(id: env.makeId())
      state.scene = .landing(landingState)
    }
    return .none

  case .landing(_), .session(_):
    return .none
  }
}
.presenting(
  landingReducer,
  state: .keyPath(\.scene.asLanding),
  id: .notNil(),
  action: /AppAction.landing,
  environment: \.landing
)
.presenting(
  sessionReducer,
  state: .keyPath(\.scene.asSession),
  id: .notNil(),
  action: /AppAction.session,
  environment: \.session
)

extension AppEnvironment {
  static let unimplemented = AppEnvironment(
    makeId: XCTUnimplemented("\(Self.self).makeId", placeholder: UUID()),
    hasCMix: XCTUnimplemented("\(Self.self).hasCMix", placeholder: Empty().eraseToAnyPublisher()),
    mainScheduler: .unimplemented,
    landing: .unimplemented,
    session: .unimplemented
  )
}