import ComposableArchitecture
import ComposablePresentation
import ContactFeature
import Foundation
import XCTestDynamicOverlay
import XXClient
import XXMessengerClient

public struct UserSearchState: Equatable {
  public enum Field: String, Hashable {
    case username
    case email
    case phone
  }

  public struct Result: Equatable, Identifiable {
    public init(
      id: Data,
      xxContact: XXClient.Contact,
      username: String? = nil,
      email: String? = nil,
      phone: String? = nil
    ) {
      self.id = id
      self.xxContact = xxContact
      self.username = username
      self.email = email
      self.phone = phone
    }

    public var id: Data
    public var xxContact: XXClient.Contact
    public var username: String?
    public var email: String?
    public var phone: String?

    public var hasFacts: Bool {
      username != nil || email != nil || phone != nil
    }
  }

  public init(
    focusedField: Field? = nil,
    query: MessengerSearchUsers.Query = .init(),
    isSearching: Bool = false,
    failure: String? = nil,
    results: IdentifiedArrayOf<Result> = [],
    contact: ContactState? = nil
  ) {
    self.focusedField = focusedField
    self.query = query
    self.isSearching = isSearching
    self.failure = failure
    self.results = results
    self.contact = contact
  }

  @BindableState public var focusedField: Field?
  @BindableState public var query: MessengerSearchUsers.Query
  public var isSearching: Bool
  public var failure: String?
  public var results: IdentifiedArrayOf<Result>
  public var contact: ContactState?
}

public enum UserSearchAction: Equatable, BindableAction {
  case searchTapped
  case didFail(String)
  case didSucceed([Contact])
  case didDismissContact
  case resultTapped(id: Data)
  case binding(BindingAction<UserSearchState>)
  case contact(ContactAction)
}

public struct UserSearchEnvironment {
  public init(
    messenger: Messenger,
    mainQueue: AnySchedulerOf<DispatchQueue>,
    bgQueue: AnySchedulerOf<DispatchQueue>,
    contact: @escaping () -> ContactEnvironment
  ) {
    self.messenger = messenger
    self.mainQueue = mainQueue
    self.bgQueue = bgQueue
    self.contact = contact
  }

  public var messenger: Messenger
  public var mainQueue: AnySchedulerOf<DispatchQueue>
  public var bgQueue: AnySchedulerOf<DispatchQueue>
  public var contact: () -> ContactEnvironment
}

#if DEBUG
extension UserSearchEnvironment {
  public static let unimplemented = UserSearchEnvironment(
    messenger: .unimplemented,
    mainQueue: .unimplemented,
    bgQueue: .unimplemented,
    contact: { .unimplemented }
  )
}
#endif

public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSearchEnvironment>
{ state, action, env in
  switch action {
  case .searchTapped:
    state.focusedField = nil
    state.isSearching = true
    state.results = []
    state.failure = nil
    return .result { [query = state.query] in
      do {
        return .success(.didSucceed(try env.messenger.searchUsers(query: query)))
      } catch {
        return .success(.didFail(error.localizedDescription))
      }
    }
    .subscribe(on: env.bgQueue)
    .receive(on: env.mainQueue)
    .eraseToEffect()

  case .didSucceed(let contacts):
    state.isSearching = false
    state.failure = nil
    state.results = IdentifiedArray(uniqueElements: contacts.compactMap { contact in
      guard let id = try? contact.getId() else { return nil }
      return UserSearchState.Result(
        id: id,
        xxContact: contact,
        username: try? contact.getFact(.username)?.fact,
        email: try? contact.getFact(.email)?.fact,
        phone: try? contact.getFact(.phone)?.fact
      )
    })
    return .none

  case .didFail(let failure):
    state.isSearching = false
    state.failure = failure
    state.results = []
    return .none

  case .didDismissContact:
    state.contact = nil
    return .none

  case .resultTapped(let id):
    state.contact = ContactState(
      id: id,
      xxContact: state.results[id: id]?.xxContact
    )
    return .none

  case .binding(_), .contact(_):
    return .none
  }
}
.binding()
.presenting(
  contactReducer,
  state: .keyPath(\.contact),
  id: .keyPath(\.?.id),
  action: /UserSearchAction.contact,
  environment: { $0.contact() }
)