import CustomDump
import SwiftUI

// NB: `BindableAction` can produce crashes in Xcode 12.4 (Swift 5.3) and earlier due to an enum
//     protocol witness bug: https://bugs.swift.org/browse/SR-14041
#if compiler(>=5.4)
  /// A property wrapper type that can designate properties of app state that can be directly
  /// bindable in SwiftUI views.
  ///
  /// Along with an action type that conforms to the ``BindableAction`` protocol, this type can be
  /// used to safely eliminate the boilerplate that is typically incurred when working with multiple
  /// mutable fields on state.
  ///
  /// For example, a settings screen may model its state with the following struct:
  ///
  /// ```swift
  /// struct SettingsState {
  ///   var digest = Digest.daily
  ///   var displayName = ""
  ///   var enableNotifications = false
  ///   var isLoading = false
  ///   var protectMyPosts = false
  ///   var sendEmailNotifications = false
  ///   var sendMobileNotifications = false
  /// }
  /// ```
  ///
  /// The majority of these fields should be editable by the view, and in the Composable
  /// Architecture this means that each field requires a corresponding action that can be sent to
  /// the store. Typically this comes in the form of an enum with a case per field:
  ///
  /// ```swift
  /// enum SettingsAction {
  ///   case digestChanged(Digest)
  ///   case displayNameChanged(String)
  ///   case enableNotificationsChanged(Bool)
  ///   case protectMyPostsChanged(Bool)
  ///   case sendEmailNotificationsChanged(Bool)
  ///   case sendMobileNotificationsChanged(Bool)
  /// }
  /// ```
  ///
  /// And we're not even done yet. In the reducer we must now handle each action, which simply
  /// replaces the state at each field with a new value:
  ///
  /// ```swift
  /// let settingsReducer = Reducer<
  ///   SettingsState, SettingsAction, SettingsEnvironment
  /// > { state, action, environment in
  ///   switch action {
  ///   case let digestChanged(digest):
  ///     state.digest = digest
  ///     return .none
  ///
  ///   case let displayNameChanged(displayName):
  ///     state.displayName = displayName
  ///     return .none
  ///
  ///   case let enableNotificationsChanged(isOn):
  ///     state.enableNotifications = isOn
  ///     return .none
  ///
  ///   case let protectMyPostsChanged(isOn):
  ///     state.protectMyPosts = isOn
  ///     return .none
  ///
  ///   case let sendEmailNotificationsChanged(isOn):
  ///     state.sendEmailNotifications = isOn
  ///     return .none
  ///
  ///   case let sendMobileNotificationsChanged(isOn):
  ///     state.sendMobileNotifications = isOn
  ///     return .none
  ///   }
  /// }
  /// ```
  ///
  /// This is a _lot_ of boilerplate for something that should be simple. Luckily, we can
  /// dramatically eliminate this boilerplate using `BindableState` and ``BindableAction``.
  ///
  /// First, we can annotate each bindable value of state with the `@BindableState` property
  /// wrapper:
  ///
  /// ```swift
  /// struct SettingsState {
  ///   @BindableState var digest = Digest.daily
  ///   @BindableState var displayName = ""
  ///   @BindableState var enableNotifications = false
  ///   var isLoading = false
  ///   @BindableState var protectMyPosts = false
  ///   @BindableState var sendEmailNotifications = false
  ///   @BindableState var sendMobileNotifications = false
  /// }
  /// ```
  ///
  /// Each annotated field is directly to bindable to SwiftUI controls, like pickers, toggles, and
  /// text fields. Notably, the `isLoading` property is _not_ annotated as being bindable, which
  /// prevents the view from mutating this value directly.
  ///
  /// Next, we can conform the action type to ``BindableAction`` by collapsing all of the
  /// individual, field-mutating actions into a single case that holds a ``BindingAction`` generic
  /// over the reducer's `SettingsState`:
  ///
  /// ```swift
  /// enum SettingsAction: BindableAction {
  ///   case binding(BindingAction<SettingsState>)
  /// }
  /// ```
  ///
  /// And then, we can simplify the settings reducer by allowing the `binding` method to handle
  /// these field mutations for us:
  ///
  /// ```swift
  /// let settingsReducer = Reducer<
  ///   SettingsState, SettingsAction, SettingsEnvironment
  /// > {
  ///   switch action {
  ///   case .binding:
  ///     return .none
  ///   }
  /// }
  /// .binding()
  /// ```
  ///
  /// Binding actions are constructed and sent to the store by calling ``ViewStore/binding(_:)``
  /// with a key path to the bindable state:
  ///
  /// ```swift
  /// TextField("Display name", text: viewStore.binding(\.$displayName))
  /// ```
  ///
  /// Should you need to layer additional functionality over these bindings, your reducer can
  /// pattern match the action for a given key path:
  ///
  /// ```swift
  /// case .binding(\.$displayName):
  ///   // Validate display name
  ///
  /// case .binding(\.$enableNotifications):
  ///   // Return an authorization request effect
  /// ```
  ///
  /// Binding actions can also be tested in much the same way regular actions are tested. Rather
  /// than send a specific action describing how a binding changed, such as
  /// `.displayNameChanged("Blob")`, you will send a ``Reducer/binding(action:)`` action that
  /// describes which key path is being set to what value, such as `.set(\.$displayName, "Blob")`:
  ///
  /// ```swift
  /// let store = TestStore(
  ///   initialState: SettingsState(),
  ///   reducer: settingsReducer,
  ///   environment: SettingsEnvironment(...)
  /// )
  ///
  /// store.send(.set(\.$displayName, "Blob")) {
  ///   $0.displayName = "Blob"
  /// }
  /// store.send(.set(\.$protectMyPosts, true)) {
  ///   $0.protectMyPosts = true
  /// )
  /// ```
  @dynamicMemberLookup
  @propertyWrapper
  public struct BindableState<Value> {
    /// The underlying value wrapped by the bindable state.
    public var wrappedValue: Value

    /// Creates bindable state from the value of another bindable state.
    public init(wrappedValue: Value) {
      self.wrappedValue = wrappedValue
    }

    /// A projection that can be used to derive bindings from a view store.
    ///
    /// Use the projected value to derive bindings from a view store with properties annotated with
    /// `@BindableState`. To get the `projectedValue`, prefix the property with `$`:
    ///
    /// ```swift
    /// TextField("Display name", text: viewStore.binding(\.$displayName))
    /// ```
    ///
    /// See ``BindableState`` for more details.
    public var projectedValue: Self {
      get { self }
      set { self = newValue }
    }

    /// Returns bindable state to the resulting value of a given key path.
    ///
    /// - Parameter keyPath: A key path to a specific resulting value.
    /// - Returns: A new bindable state.
    public subscript<Subject>(
      dynamicMember keyPath: WritableKeyPath<Value, Subject>
    ) -> BindableState<Subject> {
      get { .init(wrappedValue: self.wrappedValue[keyPath: keyPath]) }
      set { self.wrappedValue[keyPath: keyPath] = newValue.wrappedValue }
    }
  }

  extension BindableState: Equatable where Value: Equatable {}

  extension BindableState: Hashable where Value: Hashable {}

  extension BindableState: Decodable where Value: Decodable {
    public init(from decoder: Decoder) throws {
      do {
        let container = try decoder.singleValueContainer()
        self.init(wrappedValue: try container.decode(Value.self))
      } catch {
        self.init(wrappedValue: try Value(from: decoder))
      }
    }
  }

  extension BindableState: Encodable where Value: Encodable {
    public func encode(to encoder: Encoder) throws {
      do {
        var container = encoder.singleValueContainer()
        try container.encode(self.wrappedValue)
      } catch {
        try self.wrappedValue.encode(to: encoder)
      }
    }
  }

  extension BindableState: CustomReflectable {
    public var customMirror: Mirror {
      Mirror(reflecting: self.wrappedValue)
    }
  }

  extension BindableState: CustomDumpRepresentable {
    public var customDumpValue: Any {
      self.wrappedValue
    }
  }

  extension BindableState: CustomDebugStringConvertible where Value: CustomDebugStringConvertible {
    public var debugDescription: String {
      self.wrappedValue.debugDescription
    }
  }

  /// An action type that exposes a `binding` case that holds a ``BindingAction``.
  ///
  /// Used in conjunction with ``BindableState`` to safely eliminate the boilerplate typically
  /// associated with mutating multiple fields in state.
  ///
  /// See the documentation for ``BindableState`` for more details.
  public protocol BindableAction {
    /// The root state type that contains bindable fields.
    associatedtype State

    /// Embeds a binding action in this action type.
    ///
    /// - Returns: A binding action.
    static func binding(_ action: BindingAction<State>) -> Self
  }

  extension BindableAction {
    /// Constructs a binding action for the given key path and bindable value.
    ///
    /// Shorthand for `.binding(.set(\.$keyPath, value))`.
    ///
    /// - Returns: A binding action.
    public static func set<Value>(
      _ keyPath: WritableKeyPath<State, BindableState<Value>>,
      _ value: Value
    ) -> Self
    where Value: Equatable {
      self.binding(.set(keyPath, value))
    }
  }

  extension ViewStore {
    /// Returns a binding to the resulting bindable state of a given key path.
    ///
    /// - Parameter keyPath: A key path to a specific bindable state.
    /// - Returns: A new binding.
    public func binding<Value>(
      _ keyPath: WritableKeyPath<State, BindableState<Value>>
    ) -> Binding<Value>
    where Action: BindableAction, Action.State == State, Value: Equatable {
      self.binding(
        get: { $0[keyPath: keyPath].wrappedValue },
        send: { .binding(.set(keyPath, $0)) }
      )
    }
  }
#endif

/// An action that describes simple mutations to some root state at a writable key path.
///
/// Used in conjunction with ``BindableState`` and ``BindableAction`` to safely eliminate the
/// boilerplate typically associated with mutating multiple fields in state.
///
/// See the documentation for ``BindableState`` for more details.
public struct BindingAction<Root>: Equatable {
  public let keyPath: PartialKeyPath<Root>

  let set: (inout Root) -> Void
  let value: Any
  let valueIsEqualTo: (Any) -> Bool

  public static func == (lhs: Self, rhs: Self) -> Bool {
    lhs.keyPath == rhs.keyPath && lhs.valueIsEqualTo(rhs.value)
  }
}

#if compiler(>=5.4)
  extension BindingAction {
    /// Returns an action that describes simple mutations to some root state at a writable key path
    /// to bindable state.
    ///
    /// - Parameters:
    ///   - keyPath: A key path to the property that should be mutated. This property must be
    ///     annotated with the ``BindableState`` property wrapper.
    ///   - value: A value to assign at the given key path.
    /// - Returns: An action that describes simple mutations to some root state at a writable key
    ///   path.
    public static func set<Value>(
      _ keyPath: WritableKeyPath<Root, BindableState<Value>>,
      _ value: Value
    ) -> Self where Value: Equatable {
      .init(
        keyPath: keyPath,
        set: { $0[keyPath: keyPath].wrappedValue = value },
        value: value,
        valueIsEqualTo: { $0 as? Value == value }
      )
    }

    /// Matches a binding action by its key path.
    ///
    /// Implicitly invoked when switching on a reducer's action and pattern matching on a binding
    /// action directly to do further work:
    ///
    /// ```swift
    /// case .binding(\.$displayName): // Invokes the `~=` operator.
    ///   // Validate display name
    ///
    /// case .binding(\.$enableNotifications):
    ///   // Return an authorization request effect
    /// ```
    public static func ~= <Value>(
      keyPath: WritableKeyPath<Root, BindableState<Value>>,
      bindingAction: Self
    ) -> Bool {
      keyPath == bindingAction.keyPath
    }
  }
#endif

extension BindingAction {
  /// Transforms a binding action over some root state to some other type of root state given a
  /// key path.
  ///
  /// Useful in transforming binding actions on view state into binding actions on reducer state
  /// when the domain contains ``BindableState`` and ``BindableAction``.
  ///
  /// For example, we can model an app that can bind an integer count to a stepper and make a
  /// network request to fetch a fact about that integer with the following domain:
  ///
  /// ```swift
  /// struct AppState: Equatable {
  ///   @BindableState var count = 0
  ///   var fact: String?
  ///   ...
  /// }
  ///
  /// enum AppAction: BindableAction {
  ///   case binding(BindingAction<AppState>
  ///   case factButtonTapped
  ///   case factResponse(String?)
  ///   ...
  /// }
  ///
  /// struct AppEnvironment {
  ///   var numberFact: (Int) -> Effect<String, Error>
  ///   ...
  /// }
  ///
  /// let appReducer = Reducer<AppState, AppAction, AppEnvironment> {
  ///   ...
  /// }
  /// .binding()
  ///
  /// struct AppView: View {
  ///   let store: Store
  ///
  ///   var view: some View {
  ///     ...
  ///   }
  /// }
  /// ```
  ///
  /// The view may want to limit the state and actions it has access to by introducing a
  /// view-specific domain that contains only the state and actions the view needs. Not only will
  /// this minimize the number of times a view's `body` is computed, it will prevent the view
  /// from accessing state or sending actions outside its purview. We can define it with its own
  /// bindable state and bindable action:
  ///
  /// ```swift
  /// extension AppView {
  ///   struct ViewState: Equatable {
  ///     @BindableState var count: Int
  ///     let fact: String?
  ///     // no access to any other state on `AppState`, like child domains
  ///   }
  ///
  ///   enum ViewAction: BindableAction {
  ///     case binding(BindingAction<ViewState>)
  ///     case factButtonTapped
  ///     // no access to any other action on `AppAction`, like `factResponse`
  ///   }
  /// }
  /// ```
  ///
  /// In order to transform a `BindingAction<ViewState>` sent from the view domain into a
  /// `BindingAction<AppState>`, we need a writable key path from `AppState` to `ViewState`. We
  /// can synthesize one by defining a computed property on `AppState` with a getter and a setter.
  /// The setter should communicate any mutations to bindable state back to the parent state:
  ///
  /// ```swift
  /// extension AppState {
  ///   var view: AppView.ViewState {
  ///     get { .init(count: self.count, fact: self.fact) }
  ///     set { self.count = newValue.count }
  ///   }
  /// }
  /// ```
  ///
  /// With this property defined it is now possible to transform a `BindingAction<ViewState>` into
  /// a `BindingAction<AppState>`, which means we can transform a `ViewAction` into an
  /// `AppAction`. This is where `pullback` comes into play: we can unwrap the view action's
  /// binding action on view state and transform it with `pullback` to work with app state. We can
  /// define a helper that performs this transformation, as well as route any other view actions
  /// to their reducer equivalents:
  ///
  /// ```swift
  /// extension AppAction {
  ///   static func view(_ viewAction: AppView.ViewAction) -> Self {
  ///     switch viewAction {
  ///     case let .binding(action):
  ///       // transform view binding actions into app binding actions
  ///       return .binding(action.pullback(\.view))
  ///
  ///     case let .factButtonTapped
  ///       // route `ViewAction.factButtonTapped` to `AppAction.factButtonTapped`
  ///       return .factButtonTapped
  ///     }
  ///   }
  /// }
  /// ```
  ///
  /// Finally, in the view we can invoke ``Store/scope(state:action:)`` with these domain
  /// transformations to leverage the view store's binding helpers:
  ///
  /// ```swift
  /// WithViewStore(
  ///   self.store.scope(state: \.view, action: AppAction.view)
  /// ) { viewStore in
  ///   Stepper("\(viewStore.count)", viewStore.binding(\.$count))
  ///   Button("Get number fact") { viewStore.send(.factButtonTapped) }
  ///   if let fact = viewStore.fact {
  ///     Text(fact)
  ///   }
  /// }
  /// ```
  ///
  /// - Parameter keyPath: A key path from a new type of root state to the original root state.
  /// - Returns: A binding action over a new type of root state.
  public func pullback<NewRoot>(
    _ keyPath: WritableKeyPath<NewRoot, Root>
  ) -> BindingAction<NewRoot> {
    .init(
      keyPath: (keyPath as AnyKeyPath).appending(path: self.keyPath) as! PartialKeyPath<NewRoot>,
      set: { self.set(&$0[keyPath: keyPath]) },
      value: self.value,
      valueIsEqualTo: self.valueIsEqualTo
    )
  }
}

extension BindingAction: CustomDumpReflectable {
  public var customDumpMirror: Mirror {
    .init(
      self,
      children: [
        "set": (self.keyPath, self.value)
      ],
      displayStyle: .enum
    )
  }
}

#if compiler(>=5.4)
  extension Reducer where Action: BindableAction, State == Action.State {
    /// Returns a reducer that applies ``BindingAction`` mutations to `State` before running this
    /// reducer's logic.
    ///
    /// For example, a settings screen may gather its binding actions into a single
    /// ``BindingAction`` case by conforming to ``BindableAction``:
    ///
    /// ```swift
    /// enum SettingsAction: BindableAction {
    ///   ...
    ///   case binding(BindingAction<SettingsState>)
    /// }
    /// ```
    ///
    /// The reducer can then be enhanced to automatically handle these mutations for you by tacking
    /// on the ``binding()`` method:
    ///
    /// ```swift
    /// let settingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment> {
    ///   ...
    /// }
    /// .binding()
    /// ```
    ///
    /// - Returns: A reducer that applies ``BindingAction`` mutations to `State` before running this
    ///   reducer's logic.
    public func binding() -> Self {
      Self { state, action, environment in
        guard let bindingAction = (/Action.binding).extract(from: action)
        else {
          return self.run(&state, action, environment)
        }

        bindingAction.set(&state)
        return self.run(&state, action, environment)
      }
    }
  }
#endif
