/// MIT License
///
/// Copyright (c) 2020 Point-Free, Inc.
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.

import CasePaths
import Combine
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(wrappedValue)
            } catch {
                try wrappedValue.encode(to: encoder)
            }
        }
    }

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

    extension BindableState: CustomDebugStringConvertible where Value: CustomDebugStringConvertible {
        public var debugDescription: String {
            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
    }

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

public extension StoreProtocol {
    /// 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.
    func binding<Value>(
        _ keyPath: WritableKeyPath<State, BindableState<Value>>
    ) -> Binding<Value>
        where RefinedAction: BindableAction, RefinedAction.State == State, Value: Equatable
    {
        binding(
            get: { $0[keyPath: keyPath].wrappedValue },
            send: { .binding(.set(keyPath, $0)) }
        )
    }
}

/// 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.
///
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)
    public 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.
        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
        /// ```
        static func ~= <Value>(
            keyPath: WritableKeyPath<Root, BindableState<Value>>,
            bindingAction: Self
        ) -> Bool {
            keyPath == bindingAction.keyPath
        }
    }
#endif

public 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.
    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: value,
            valueIsEqualTo: valueIsEqualTo
        )
    }
}

#if compiler(>=5.4)
    public extension Reducer where RefinedAction: BindableAction, State == RefinedAction.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.
        func binding() -> Self {
            Self { state, action, environment in
                guard let bindingAction = (/RefinedAction.binding).extract(from: action) else {
                    return self(state: &state, action: action, environment: environment)
                }
                bindingAction.set(&state)
                self(state: &state, action: action, environment: environment)
            }
        }
    }
#endif

public extension StoreProtocol {
    /// Create a SwiftUI Binding from a lensing function and a `RefinedAction`.
    /// - Parameters:
    ///   - lens: A lens to the state property.
    ///   - action: The refined action which will be called when the value is changed.
    /// - Returns: A `Binding` whose getter is the property and whose setter dispatches the refined action.
    func binding<Value>(
        get lens: @escaping (State) -> Value,
        send transform: @escaping (Value) -> RefinedAction
    ) -> Binding<Value> {
        .init(
            get: { lens(state) },
            set: { dispatch(refined: transform($0)) }
        )
    }

    /// Create a SwiftUI Binding from the `State` of the store and a `RefinedAction`.
    /// - Parameters:
    ///   - action: The refined action which will be called when the value is changed.
    /// - Returns: A `Binding` whose getter is the state and whose setter dispatches the refined action.
    func binding(
        send transform: @escaping (State) -> RefinedAction
    ) -> Binding<State> {
        binding(
            get: { $0 },
            send: transform
        )
    }

    /// Create a SwiftUI Binding from a lensing function when the value of that function is equivalent to `RefinedAction`.
    /// - Parameters:
    ///   - get: A lens to the state property.
    /// - Returns: A `Binding` whose getter is the property and whose setter dispatches the store's refined action.
    func binding<Value>(
        get lens: @escaping (State) -> Value
    ) -> Binding<Value> where RefinedAction == Value {
        binding(
            get: lens,
            send: { $0 }
        )
    }

    /// Create a SwiftUI Binding from the `State` when its value is equivalent to `RefinedAction`.
    /// - Returns: A `Binding` whose getter is the state and whose setter dispatches the store's refined action.
    func binding() -> Binding<State> where RefinedAction == State {
        binding(get: { $0 })
    }
}

public extension StoreProtocol {
    /// Create a SwiftUI Binding from a lensing function and a `RawAction`.
    /// - Parameters:
    ///   - lens: A lens to the state property.
    ///   - action: The refined action which will be called when the value is changed.
    /// - Returns: A `Binding` whose getter is the property and whose setter dispatches the refined action.
    func binding<Value>(
        get lens: @escaping (State) -> Value,
        send transform: @escaping (Value) -> RawAction
    ) -> Binding<Value> {
        .init(
            get: { lens(state) },
            set: { dispatch(raw: transform($0)) }
        )
    }

    /// Create a SwiftUI Binding from the `State` of the store and a `RawAction`.
    /// - Parameters:
    ///   - action: The refined action which will be called when the value is changed.
    /// - Returns: A `Binding` whose getter is the state and whose setter dispatches the refined action.
    func binding(
        send transform: @escaping (State) -> RawAction
    ) -> Binding<State> {
        binding(
            get: { $0 },
            send: transform
        )
    }
}
