/**
 Notes about paging
  As of the time of writing there are many endpoints that support paging however they are not all consistent:
  - Some start paging at 0
  - Some start paging at 1
  - Some return the same page regardless of page number
  - Most return an empty array when you reach the end but some throw an error

  This state object attempts to contain and unify this logic as much as possible.
  Generally the only thing you need to do for a usage standpoint is:
  1. Init with the `firstPageIndex` of the api you are calling,
     this _should_ be the only part that may differ.. _Most_ will the 1-indexed.
  2. When the feature using this state starts up or pull to refresh is triggered call `reset()` to clear the state
  3. When getting the next page use the `nextIndex()` function to get the index needed for the api call.
     It will be `nil` if the logic in here has determined you are at the end.
  4. If an api call succeeds, call `update(_:)` with the array of items in the page
  5. If an api call fails, call `update(_:)` with the received error
  5. The view can use `hasMore` to determine if the loading UI should be shown
     and use `loadingNext` to ensure only 1 request is made at a time.
  */
public struct PageState: Equatable, Hashable {
    public enum NextPageStrategy {
        case `default`, count
    }

    private var allowAllPages = true
    private var index: Int
    public let firstPageIndex: Int
    public private(set) var loadingNext = false
    public private(set) var hasMore = true
    public var nextPage: NextPageStrategy = .default
    private var lastIds: [AnyHashable] = []

    public init(firstPageIndex: Int) {
        self.firstPageIndex = firstPageIndex
        self.index = firstPageIndex - 1
    }

    public mutating func reset() {
        lastIds = []
        index = firstPageIndex - 1
    }

    public mutating func nextIndex() -> Int? {
        guard !loadingNext else { return nil }
        if !allowAllPages && index >= firstPageIndex { return nil }

        loadingNext = true
        return index + 1
    }

    public mutating func update<T: Collection>(_ values: T, hasAllResults: Bool? = nil) where T.Element: Identifiable {
        let newIds = values.map { AnyHashable($0.id) }
        let newPage = newIds != lastIds

        hasMore = {
            if let hasAllResults {
                return allowAllPages ? (newPage && !values.isEmpty && !hasAllResults) : false
            } else {
                return allowAllPages ? (newPage && !values.isEmpty) : false
            }
        }()

        loadingNext = false
        switch nextPage {
        case .default:
            index += 1
        case .count:
            index += values.count - 1
        }
        lastIds = newIds
    }

    public mutating func update(_: Error) {
        hasMore = false
        loadingNext = false
    }
}
