import APIClient
import ComponentLibrary
import ComposableArchitecture
import Foundation

public extension HooksFeedReducer.State {
    /*
       Memory-efficient bounded UI state inspired by Spotify architecture.
       Cache (SimpleLikeCache, SimpleFollowCache) is the single source of truth.

       Strategy:
       - Always accept legitimate events (no race conditions)
       - Automatically cleanup old items during scroll to keep state bounded
       - Maintains window of ±25 items around current position
     */
    @ObservableState
    struct HooksMetadataState: Equatable {
        public var followStatus: [String: Bool] = [:] // Creator Handle -> follow status
        public var likeStatus: [String: Bool] = [:] // Hook ID -> like status
        public var likeCount: [String: Int] = [:] // Hook ID -> like count
        public var commentCount: [String: Int] = [:] // Hook ID -> comment count
        public var clipLikeStatus: [String: Bool] = [:] // Hook ID -> clip like status
        public var clipPlayCount: [String: Int] = [:] // Hook ID -> clip play count
        public var reportedStatus: [String: Bool] = [:] // Hook ID -> isReported
        public var hiddenCreatorHandles: [String: Bool] = [:] // Creator handle -> isHidden
        public var dislikeStatus: [String: Bool] = [:] // Hook ID -> isDisliked
        public var lyrics: [String: LyricsDataV2] = [:] // Hook ID -> lyrics data
        public var allowCommentsStatus: [String: Bool] = [:] // Hook ID -> allowComments

        // Set of hookIds and handles that are kept in state
        internal var trackedHookIds: Set<String> = []
        internal var trackedHandles: Set<String> = []

        private let bufferSize = 25

        private var currentWindowStart: Int = -1
        private var currentWindowEnd: Int = -1

        public init() {}

        public func getIsFollowing(for handle: String) -> Bool? {
            return followStatus[handle]
        }

        public func getIsLiked(for hookId: String) -> Bool? {
            return likeStatus[hookId]
        }

        public func getLikeCount(for hookId: String) -> Int? {
            return likeCount[hookId]
        }

        public func getCommentCount(for hookId: String) -> Int? {
            return commentCount[hookId]
        }

        public func getClipLikeStatus(for hookId: String) -> Bool? {
            return clipLikeStatus[hookId]
        }

        public func getIsReported(for hookId: String) -> Bool {
            return reportedStatus[hookId] ?? false
        }

        public func getIsCreatorHidden(for handle: String) -> Bool {
            return hiddenCreatorHandles[handle] ?? false
        }

        public func getIsDisliked(for hookId: String) -> Bool {
            return dislikeStatus[hookId] ?? false
        }

        public func getAllowComments(for hookId: String) -> Bool? {
            return allowCommentsStatus[hookId]
        }

        public func getLyrics(for hookId: String) -> LyricsDataV2? {
            return lyrics[hookId]
        }

        public func getClipPlayCount(for hookId: String) -> Int? {
            return clipPlayCount[hookId]
        }

        // Evicts old items and fetches new items that we need to sync from cache
        // This is only called if we're not within `bufferSize` from the current index
        public mutating func hydrateIfNeeded(hooks: IdentifiedArrayOf<Hook>, currentIndex: Int, forceSync: Bool = false) -> (newHookIds: [String], newHandles: [String]) {
            guard !hooks.isEmpty else {
                currentWindowStart = -1
                currentWindowEnd = -1
                return (newHookIds: [], newHandles: [])
            }

            let startIndex = max(0, currentIndex - bufferSize)
            let endIndex = min(hooks.count - 1, currentIndex + bufferSize)

            // Early exit if we're still within the same window of 25 hooks,
            // or when calling `forceSync` on first load
            if !forceSync &&
                currentIndex >= currentWindowStart &&
                currentIndex <= currentWindowEnd &&
                startIndex >= currentWindowStart &&
                endIndex <= currentWindowEnd
            {
                return (newHookIds: [], newHandles: [])
            }

            var newTrackedHookIds: Set<String> = []
            var newTrackedHandles: Set<String> = []

            for i in startIndex ... endIndex {
                guard i < hooks.count else { continue }
                let hook = hooks[i]
                newTrackedHookIds.insert(hook.id)

                guard let handle = hook.user?.handle else { continue }
                newTrackedHandles.insert(handle)
            }

            // Find items that need to be synced from cache
            let newHookIds: [String]
            let newHandles: [String]

            if forceSync {
                // Force sync all items in current window
                newHookIds = Array(newTrackedHookIds)
                newHandles = Array(newTrackedHandles)
            } else {
                // Only sync truly new items
                newHookIds = Array(newTrackedHookIds.subtracting(trackedHookIds))
                newHandles = Array(newTrackedHandles.subtracting(trackedHandles))
            }

            // Evict old items outside visible window
            let hooksToEvict = trackedHookIds.subtracting(newTrackedHookIds)
            let handlesToEvict = trackedHandles.subtracting(newTrackedHandles)

            for hookId in hooksToEvict {
                likeStatus.removeValue(forKey: hookId)
                likeCount.removeValue(forKey: hookId)
                commentCount.removeValue(forKey: hookId)
                clipPlayCount.removeValue(forKey: hookId)
                allowCommentsStatus.removeValue(forKey: hookId)
            }

            for handle in handlesToEvict {
                followStatus.removeValue(forKey: handle)
            }

            trackedHookIds = newTrackedHookIds
            trackedHandles = newTrackedHandles

            // Update window bounds for future early exits
            currentWindowStart = startIndex
            currentWindowEnd = endIndex

            return (newHookIds: newHookIds, newHandles: newHandles)
        }
    }
}
