import APIClient
import AVFoundation
import ComponentLibrary
import ComposableArchitecture
import HooksPlayerClient
import UIKit
import Utilities

public final class HooksFeedViewController: UIViewController {
    private lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        layout.minimumLineSpacing = 0
        layout.minimumInteritemSpacing = 0

        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = UIColor.SemanticV1.backgroundPrimary
        cv.isPagingEnabled = true
        cv.showsVerticalScrollIndicator = false
        cv.decelerationRate = .fast
        cv.delegate = self
        cv.dataSource = self
        cv.contentInsetAdjustmentBehavior = .never
        cv.register(HookVideoCell.self, forCellWithReuseIdentifier: "HookVideoCell")
        return cv
    }()

    private var hooks: [Hook] = []
    private var currentIndex: Int = 0
    private var feedId: UUID
    private var meHandle: String = ""
    private var followStatus: [String: Bool] = [:]
    private var likeStatus: [String: Bool] = [:]
    private var likeCount: [String: Int] = [:]
    private var clipLikeStatus: [String: Bool] = [:]
    private var clipPlayCount: [String: Int] = [:]
    private var commentCount: [String: Int] = [:]
    private var reportedStatus: [String: Bool] = [:]
    private var hiddenCreatorHandles: [String: Bool] = [:]
    private var dislikeStatus: [String: Bool] = [:]
    private var isFocused: Bool = false
    private var isMuted: Bool = true
    private var lyricsStatus: [String: LyricsDataV2] = [:]
    private var playingHookId: String?
    private var lastWorkingRange: Set<Int> = []
    private var wasScrolling: Bool = false

    public var onAction: ((HooksFeedReducer.Action) -> Void)?

    @Dependency(\.hooksPlayerClient) var hooksPlayerClient

    public init(feedId: UUID) {
        self.feedId = feedId
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Lifecycle

    override public func viewDidLoad() {
        super.viewDidLoad()
        setupCollectionView()
    }

    override public func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Sync follow status from cache when returning to hooks feed
        syncFollowStatusFromCache()
    }

    private func syncFollowStatusFromCache() {
        Task { @MainActor in
            let handles = Set(hooks.compactMap { $0.user?.handle })
            let metadata = await hooksPlayerClient.syncHooksMetadata(Array(hooks.map(\.id)), Array(handles))

            for (handle, isFollowing) in metadata.followStatuses {
                followStatus[handle] = isFollowing
            }
            for (hookId, isLiked) in metadata.likeStatuses {
                likeStatus[hookId] = isLiked
            }
            for (hookId, count) in metadata.likeCounts {
                likeCount[hookId] = count
            }
            for (hookId, count) in metadata.commentCounts {
                commentCount[hookId] = count
            }
            for (hookId, isLiked) in metadata.clipLikeStatuses {
                clipLikeStatus[hookId] = isLiked
            }
            for (hookId, playCount) in metadata.clipPlayCounts {
                clipPlayCount[hookId] = playCount
            }
            for (hookId, lyrics) in metadata.lyrics {
                lyricsStatus[hookId] = lyrics
            }

            refreshVisibleCells()
        }
    }

    private func refreshVisibleCells() {
        for indexPath in collectionView.indexPathsForVisibleItems {
            if let cell = collectionView.cellForItem(at: indexPath) as? HookVideoCell {
                let hook = hooks[indexPath.item]
                let handle = hook.user?.handle ?? ""

                cell.updateFollowStatus(followStatus[handle] ?? false)
                cell.updateLikeStatus(likeStatus[hook.id] ?? false)
                cell.updateLikeCount(likeCount[hook.id] ?? hook.likeCount)
                cell.updateCommentCount(commentCount[hook.id] ?? hook.commentCount)
                cell.updateClipLiked(clipLikeStatus[hook.id] ?? false)
                cell.updateClipPlayCount(clipPlayCount[hook.id] ?? hook.clip?.playCount ?? 0)
                cell.updateReportedStatus(reportedStatus[hook.id] ?? false)
                cell.updateCreatorHiddenStatus(hiddenCreatorHandles[handle] ?? false)
                cell.updateDislikeStatus(dislikeStatus[hook.id] ?? false)
                cell.updateLyrics(lyricsStatus[hook.id] ?? .empty)
            }
        }
    }

    override public func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
    }

    // MARK: - Setup

    private func setupCollectionView() {
        view.backgroundColor = UIColor.SemanticV1.backgroundPrimary
        view.addSubview(collectionView)
        setupConstraints()
    }

    private func setupConstraints() {
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }

    // MARK: - Public Interface

    public func updateHooks(
        _ hooks: [Hook],
        startIndex: Int = 0,
        feedId: UUID,
        reload: Bool = false
    ) {
        self.hooks = hooks
        self.feedId = feedId

        hooksPlayerClient.updateHooks(hooks, startIndex, reload, feedId)

        if !hooks.isEmpty, startIndex >= 0, startIndex < hooks.count {
            currentIndex = startIndex
        }

        collectionView.reloadData()

        if !hooks.isEmpty, startIndex >= 0, startIndex < hooks.count {
            let indexPath = IndexPath(item: startIndex, section: 0)
            collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
        }

        updateCellTransforms()
    }

    public func reloadHooks() {
        collectionView.reloadData()
        updateCellTransforms()
    }

    public func appendHooks(_ newHooks: [Hook]) {
        self.hooks.append(contentsOf: newHooks)
        hooksPlayerClient.appendHooks(newHooks)

        let startIndex = self.hooks.count - newHooks.count
        let indexPaths = newHooks.enumerated().map { offset, _ in
            IndexPath(item: startIndex + offset, section: 0)
        }
        collectionView.insertItems(at: indexPaths)
    }

    public func replaceHooksAfterIndex(_ afterIndex: Int, with newHooks: [Hook]) {
        let oldCount = hooks.count
        let newCount = newHooks.count

        // Calculate what needs to be deleted and inserted
        let removeStartIndex = afterIndex + 1
        let oldItemsToRemove = max(0, oldCount - removeStartIndex)
        let newItemsToAdd = max(0, newCount - removeStartIndex)

        self.hooks = newHooks

        // Perform batch update to handle count changes properly
        collectionView.performBatchUpdates {
            // Update the data source first

            // Handle deletions and insertions
            if oldItemsToRemove > 0 {
                let deleteIndexPaths = (removeStartIndex ..< (removeStartIndex + oldItemsToRemove)).map {
                    IndexPath(item: $0, section: 0)
                }
                collectionView.deleteItems(at: deleteIndexPaths)
            }

            if newItemsToAdd > 0 {
                let insertIndexPaths = (removeStartIndex ..< (removeStartIndex + newItemsToAdd)).map {
                    IndexPath(item: $0, section: 0)
                }
                collectionView.insertItems(at: insertIndexPaths)
            }
        } completion: { [self] _ in
            hooksPlayerClient.updateHooks(newHooks, afterIndex, false, feedId)
        }
    }

    public func updateCurrentIndex(_ index: Int) {
        guard index >= 0, index < hooks.count else { return }

        currentIndex = index

        hooksPlayerClient.updateCurrentIndex(index)

        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let indexPath = collectionView.indexPath(for: cell),
                  indexPath.item < hooks.count else { continue }

            let isVisible = indexPath.item == index
            hookCell.updateVisibility(isVisible)
        }

        updateCellTransforms()
    }

    public func animateToIndex(_ index: Int) {
        guard index >= 0, index < hooks.count, index != currentIndex else { return }

        currentIndex = index

        // Update HooksPlayerClient with the new index
        hooksPlayerClient.updateCurrentIndex(index)

        let indexPath = IndexPath(item: index, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)

        updateCellTransforms()
    }

    public func updateMeHandle(_ meHandle: String) {
        self.meHandle = meHandle
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook,
                  let handle = hook.user?.handle else { continue }

            let isOwner = !meHandle.isEmpty && meHandle == handle
            hookCell.updateIsOwner(isOwner)
        }
        refreshVisibleCells()
    }

    public func updateFollowStatus(_ followStatus: [String: Bool]) {
        // Find which handles actually changed to avoid mass updates
        let changedHandles = followStatus.compactMap { handle, newStatus in
            self.followStatus[handle] != newStatus ? handle : nil
        }

        self.followStatus = followStatus

        // Only update cells for handles that actually changed
        guard !changedHandles.isEmpty else { return }

        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook,
                  let handle = hook.user?.handle,
                  changedHandles.contains(handle) else { continue }

            let isFollowing = followStatus[handle] ?? false
            hookCell.updateFollowStatus(isFollowing)
        }
    }

    public func updateLikeStatus(_ likeStatus: [String: Bool]) {
        // Find which hooks actually changed to avoid mass updates
        let changedHookIds = likeStatus.compactMap { hookId, newStatus in
            self.likeStatus[hookId] != newStatus ? hookId : nil
        }

        self.likeStatus = likeStatus

        // Only update cells for hooks that actually changed
        guard !changedHookIds.isEmpty else { return }

        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook,
                  changedHookIds.contains(hook.id) else { continue }

            let isLiked = likeStatus[hook.id] ?? false
            hookCell.updateLikeStatus(isLiked)
        }
    }

    public func updateLikeCount(_ likeCount: [String: Int]) {
        // Find which hooks actually changed to avoid mass updates
        let changedHookIds = likeCount.compactMap { hookId, newCount in
            self.likeCount[hookId] != newCount ? hookId : nil
        }

        self.likeCount = likeCount

        // Only update cells for hooks that actually changed
        guard !changedHookIds.isEmpty else { return }

        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook,
                  changedHookIds.contains(hook.id) else { continue }

            let count = likeCount[hook.id] ?? 0
            hookCell.updateLikeCount(count)
        }
    }

    public func refreshHooksFeed() {
        collectionView.reloadData()

        // Use the current internal index as the source of truth during refresh
        Task { @MainActor in
            let targetIndex = self.currentIndex

            // Make sure the target index is within bounds of current hooks array
            let safeIndex = min(targetIndex, max(0, hooks.count - 1))

            // Only update currentIndex, without making playback changes
            self.currentIndex = safeIndex

            // Only scroll if we have hooks to scroll to
            if !hooks.isEmpty && safeIndex >= 0 && safeIndex < hooks.count {
                let indexPath = IndexPath(item: safeIndex, section: 0)
                collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
            }
        }
    }

    public func updateClipLikeStatus(_ clipLikeStatus: [String: Bool]) {
        // Store the clip like status
        self.clipLikeStatus = clipLikeStatus

        // Update all visible cells with new clip like count
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }

            let isLiked = clipLikeStatus[hook.id] ?? false
            hookCell.updateClipLiked(isLiked)
        }
    }

    public func updateCommentCount(_ commentCount: [String: Int]) {
        // Store the comment count
        self.commentCount = commentCount

        // Update all visible cells with like count
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }

            let count = commentCount[hook.id] ?? 0
            hookCell.updateCommentCount(count)
        }
    }

    public func updateClipPlayCount(_ clipPlayCount: [String: Int]) {
        // Store the play count
        self.clipPlayCount = clipPlayCount

        // Update all visible cells with play count
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }

            let count = clipPlayCount[hook.id] ?? hook.clip?.playCount ?? 0
            hookCell.updateClipPlayCount(count)
        }
    }

    public func updateReportedStatus(_ reportedStatus: [String: Bool]) {
        // Store the reported status
        self.reportedStatus = reportedStatus

        // Update all visible cells with reported status
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }

            let isReported = reportedStatus[hook.id] ?? false
            hookCell.updateReportedStatus(isReported)

            // If this is the current cell and it was just unreported, update its playing state
            if hookCell.index == currentIndex, !isReported {
                Task { @MainActor in
                    let playbackState = await self.hooksPlayerClient.getPlaybackState(hookCell.index)
                    hookCell.updatePlaybackState(playbackState)
                }
            }
        }
    }

    public func updateHiddenCreatorHandles(_ hiddenCreatorHandles: [String: Bool]) {
        // Store the hidden creator handles
        self.hiddenCreatorHandles = hiddenCreatorHandles

        // Update all visible cells with hidden creator status
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook,
                  let handle = hook.user?.handle else { continue }

            let isCreatorHidden = hiddenCreatorHandles[handle] ?? false
            hookCell.updateCreatorHiddenStatus(isCreatorHidden)
        }
    }

    public func updateDislikeStatus(_ dislikeStatus: [String: Bool]) {
        // Store the dislike status
        self.dislikeStatus = dislikeStatus

        // Update all visible cells with dislike status
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }

            let isDisliked = dislikeStatus[hook.id] ?? false
            hookCell.updateDislikeStatus(isDisliked)
        }
    }

    public func updateFocusedStatus(_ isFocused: Bool) {
        self.isFocused = isFocused

        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell else { continue }
            hookCell.updateFocusedStatus(isFocused)
        }
    }

    // MARK: - Player Management

    public func updateIsMuted(_ isMuted: Bool) {
        // Store the mute state
        self.isMuted = isMuted

        // Update all visible cells
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell else { continue }
            hookCell.updateIsMuted(isMuted)
        }
    }

    public func updateLyrics(_ lyrics: [String: LyricsDataV2]) {
        // Store the lyrics
        self.lyricsStatus = lyrics

        // Update all visible cells with lyrics
        for cell in collectionView.visibleCells {
            guard let hookCell = cell as? HookVideoCell,
                  let hook = hookCell.hook else { continue }
            let lyricsValue = lyrics[hook.id] ?? .empty
            hookCell.updateLyrics(lyricsValue)
        }
    }

    // MARK: - Event Handling

    public func updateCellPlayer(for index: Int) {
        let indexPath = IndexPath(item: index, section: 0)
        guard let cell = collectionView.cellForItem(at: indexPath) as? HookVideoCell else {
            return
        }

        Task { @MainActor in
            guard let player = await hooksPlayerClient.getPlayerForIndex(index) else { return }
            cell.playbackState = .ready
            cell.refreshPlayer(player)
        }
    }

    public func updateCurrentPlaybackState(for index: Int, playbackState: PlaybackState) {
        let indexPath = IndexPath(item: index, section: 0)
        guard let cell = collectionView.cellForItem(at: indexPath) as? HookVideoCell else {
            return
        }

        cell.updatePlaybackState(playbackState)
    }

    public func updatePlayingHookId(_ newPlayingHookId: String?) {
        let oldPlayingHookId = playingHookId
        playingHookId = newPlayingHookId

        if let oldId = oldPlayingHookId, oldId != newPlayingHookId {
            // Find and update the hook that stopped playing
            if let oldIndex = hooks.firstIndex(where: { $0.id == oldId }) {
                updateCurrentPlaybackState(for: oldIndex, playbackState: .ready)
            }
        }

        if let newId = newPlayingHookId, newId != oldPlayingHookId {
            // Find and update the hook that started playing
            if let newIndex = hooks.firstIndex(where: { $0.id == newId }) {
                updateCurrentPlaybackState(for: newIndex, playbackState: .playing)
            }
        }
    }

    private func updateCellTransforms() {
        let collectionViewCenter = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)
        let maxDistance = collectionView.bounds.height / 2
        let inverseMaxDistance = 1.0 / maxDistance

        // Only animate the current, previous and next cells
        let indicesToUpdate = [currentIndex - 1, currentIndex, currentIndex + 1]
            .filter { $0 >= 0 && $0 < hooks.count }

        for index in indicesToUpdate {
            let indexPath = IndexPath(item: index, section: 0)
            guard let cell = collectionView.cellForItem(at: indexPath) as? HookVideoCell else { continue }

            // Calculate cell's position relative to collection view center
            let cellCenter = cell.convert(CGPoint(x: cell.bounds.midX, y: cell.bounds.midY), to: collectionView)
            let distance = abs(cellCenter.y - collectionViewCenter.y)

            // Calculate progress (0 = center, 1 = edge)
            let progress = min(distance * inverseMaxDistance, 1.0)

            let scale = 1.0 - (progress * 0.03)
            let opacity = 1.0 - (progress * 0.5)

            cell.updateTransforms(opacity: opacity, scale: scale)
        }
    }

    private func animateEnd() {
        UIView.animate(
            withDuration: 0.3,
            delay: 0,
            usingSpringWithDamping: 0.9,
            initialSpringVelocity: 0.3,
            options: [.allowUserInteraction],
            animations: {
                self.updateCellTransforms()
            }
        )
    }

    public func insertHook(_ hook: Hook, at index: Int) {
        guard index >= 0, index <= hooks.count else { return }

        var newHooks = hooks
        newHooks.insert(hook, at: index)

        UIView.performWithoutAnimation {
            collectionView.performBatchUpdates {
                hooks = newHooks
                collectionView.insertItems(at: [IndexPath(item: index, section: 0)])
            }
        }
    }
}

// MARK: - UICollectionViewDataSource

extension HooksFeedViewController: UICollectionViewDataSource {
    public func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
        return hooks.count
    }

    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HookVideoCell", for: indexPath) as! HookVideoCell

        let hook = hooks[indexPath.item]
        let isVisible = indexPath.item == currentIndex

        let isFollowing: Bool = {
            guard let handle = hook.user?.handle else { return false }
            return followStatus[handle] ?? false
        }()

        let isLiked: Bool = likeStatus[hook.id] ?? false
        let likeCountValue: Int = likeCount[hook.id] ?? 0
        let isClipLiked: Bool = clipLikeStatus[hook.id] ?? false
        let clipPlayCountValue: Int = clipPlayCount[hook.id] ?? hook.clip?.playCount ?? 0
        let commentCountValue: Int = commentCount[hook.id] ?? hook.commentCount
        let isReported: Bool = reportedStatus[hook.id] ?? false
        let lyricsValue: LyricsDataV2 = lyricsStatus[hook.id] ?? .empty
        let isCreatorHidden: Bool = {
            guard let handle = hook.user?.handle else { return false }
            return hiddenCreatorHandles[handle] ?? false
        }()

        // Configure cell with initial values, async updates will happen below
        cell.configure(
            with: hook,
            index: indexPath.item,
            isVisible: isVisible,
            playbackState: .loading,
            showFollowButton: {
                let shouldShow = !meHandle.isEmpty && meHandle != hook.user?.handle
                return shouldShow
            }(),
            isFollowing: isFollowing,
            isLiked: isLiked,
            isClipLiked: isClipLiked,
            likeCount: likeCountValue,
            commentCount: commentCountValue,
            clipPlayCount: clipPlayCountValue,
            lyrics: lyricsValue,
            isReported: isReported,
            isFeedFocused: isFocused,
            isCreatorHidden: isCreatorHidden,
            onTapCallback: { [weak self] tappedIndex in
                self?.handleCellTap(at: tappedIndex)
            },
            onDoubleTapCallback: { [weak self] hook in
                self?.onAction?(.didDoubleTap(hook))
            },
            onRemixTapped: { [weak self] hook in
                self?.onAction?(.remixTapped(hook))
            },
            onLikeTapped: { [weak self] hook in
                self?.onAction?(.likeTapped(hook))
            },
            onFollowTapped: { [weak self] hook in
                guard let handle = hook.user?.handle else { return }
                self?.onAction?(.followTapped(handle: handle, unfollow: nil, hook: hook))
            },
            onShareTapped: { [weak self] hook in
                self?.onAction?(.shareTapped(hook))
            },
            onCommentsTapped: { [weak self] hook in
                self?.onAction?(.commentsTapped(hook))
            },
            onMoreTapped: { [weak self] hook in
                self?.onAction?(.moreTapped(hook))
            },
            onAuthorTapped: { [weak self] handle in
                self?.onAction?(.authorTapped(handle: handle))
            },
            onAddSongTapped: { [weak self] hook, isClipLiked in
                self?.onAction?(.addSongToLikesTapped(hook, isClipLiked))
            },
            onChangePlaylistTapped: { [weak self] hook in
                self?.onAction?(.changePlaylistTapped(hook))
            },
            onSongTapped: { [weak self] clip in
                self?.onAction?(.songTapped(clip))
            },
            onShowReportedHookTapped: { [weak self] hookId in
                self?.onAction?(.showReportedHook(hookId))
            },
            isMuted: isMuted,
            onTapToUnmute: { [weak self] in
                self?.onAction?(.tapToUnmute)
            }
        )

        Task {
            let player = await hooksPlayerClient.getPlayerForIndex(indexPath.item)

            await MainActor.run {
                let isReady = player?.currentItem?.status == .readyToPlay

                if let player = player {
                    cell.setPlayer(player)
                    // Only update playback state if it's not already playing
                    if cell.playbackState != .playing {
                        cell.playbackState = isReady ? .ready : .loading
                    }
                } else {
                    cell.playbackState = .notReady
                }
            }
        }

        // Preload upcoming cells that aren't visible yet
        preloadUpcomingCells(around: indexPath.item)

        return cell
    }

    private func preloadUpcomingCells(around index: Int) {
        let preloadRange = 2

        for i in (index - preloadRange) ... (index + preloadRange) {
            guard i >= 0, i < hooks.count, i != index else { continue }

            let indexPath = IndexPath(item: i, section: 0)
            if collectionView.indexPathsForVisibleItems.contains(indexPath) {
                continue
            }

            Task {
                _ = await hooksPlayerClient.getPlayerForIndex(i)
            }
        }
    }

    // For unified feed, we should just use IGListKit
    private func updateWorkingRange() {
        guard !hooks.isEmpty else { return }

        let visibleIndexPaths = collectionView.indexPathsForVisibleItems
        guard !visibleIndexPaths.isEmpty,
              let firstVisible = visibleIndexPaths.map(\.item).min(),
              let lastVisible = visibleIndexPaths.map(\.item).max(),
              firstVisible >= 0,
              lastVisible < hooks.count
        else {
            return
        }

        let leadingRange = 2
        let trailingRange = 2

        let workingRangeStart = max(0, firstVisible - trailingRange)
        let workingRangeEnd = min(hooks.count - 1, lastVisible + leadingRange)

        guard workingRangeStart <= workingRangeEnd, workingRangeStart >= 0, workingRangeEnd < hooks.count else {
            return
        }

        let newWorkingRange = Set(workingRangeStart ... workingRangeEnd)

        guard newWorkingRange != lastWorkingRange else {
            return
        }

        let entered = newWorkingRange.subtracting(lastWorkingRange)

        for index in entered {
            Task {
                _ = await hooksPlayerClient.getPlayerForIndex(index)
            }
        }

        lastWorkingRange = newWorkingRange
    }

    private func handleCellTap(at index: Int) {
        guard index < hooks.count else { return }

        // If tapping the current video, toggle play/pause
        if index == currentIndex {
            hooksPlayerClient.togglePlayPause()
        } else {
            // If tapping a different video, switch to it
            onAction?(.setCurrentIndex(index))
        }
    }
}

// MARK: - UICollectionViewDelegate

extension HooksFeedViewController: UICollectionViewDelegate {
    public func collectionView(_: UICollectionView, willDisplay _: UICollectionViewCell, forItemAt _: IndexPath) {
        updateWorkingRange()
    }

    public func collectionView(_: UICollectionView, didEndDisplaying _: UICollectionViewCell, forItemAt _: IndexPath) {
        updateWorkingRange()
    }
}

// MARK: - UICollectionViewDelegateFlowLayout

extension HooksFeedViewController: UICollectionViewDelegateFlowLayout {
    public func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize {
        let height = collectionView.bounds.height
        let size = CGSize(width: collectionView.bounds.width, height: height)
        return size
    }
}

// MARK: - UIScrollViewDelegate

extension HooksFeedViewController: UIScrollViewDelegate {
    public func scrollViewWillBeginDragging(_: UIScrollView) {
        // Tap to unmute if muted and we're scrolling
        if isMuted {
            onAction?(.tapToUnmute)
        }
    }

    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateWorkingRange()
        updateCellTransforms()

        let isCurrentlyScrolling = scrollView.contentOffset.y > 0

        if isCurrentlyScrolling, !wasScrolling {
            onAction?(.didStartScrolling)
        } else if !isCurrentlyScrolling, wasScrolling {
            onAction?(.scrollDidBecomeNeutral)
        }

        wasScrolling = isCurrentlyScrolling

        if currentIndex == 0, scrollView.contentOffset.y < 0 {
            scrollView.setContentOffset(.zero, animated: false)
        }
    }

    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let centerPoint = CGPoint(x: scrollView.bounds.midX, y: scrollView.bounds.midY)
        guard let indexPath = collectionView.indexPathForItem(at: centerPoint) else { return }
        onAction?(.setCurrentIndex(indexPath.item))
        animateEnd()
    }

    public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let cellHeight = collectionView.bounds.height
        let currentOffset = scrollView.contentOffset.y
        let targetOffset = targetContentOffset.pointee.y

        let currentIndex = Int(round(currentOffset / cellHeight))
        let targetIndex = Int(round(targetOffset / cellHeight))

        let maxIndexChange = 1
        let actualTargetIndex: Int

        if targetIndex > currentIndex {
            actualTargetIndex = min(targetIndex, currentIndex + maxIndexChange)
        } else {
            actualTargetIndex = max(targetIndex, currentIndex - maxIndexChange)
        }

        targetContentOffset.pointee.y = CGFloat(actualTargetIndex) * cellHeight

        if abs(velocity.y) > 500 {
            targetContentOffset.pointee = scrollView.contentOffset

            let finalOffset = CGFloat(actualTargetIndex) * cellHeight
            UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) {
                scrollView.setContentOffset(CGPoint(x: 0, y: finalOffset), animated: false)
            }
        }
    }

    public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            let centerPoint = CGPoint(x: scrollView.bounds.midX, y: scrollView.bounds.midY)
            guard let indexPath = collectionView.indexPathForItem(at: centerPoint) else { return }
            onAction?(.setCurrentIndex(indexPath.item))
            animateEnd()
        }
    }

    // MARK: - Scroll Control

    public func setScrollEnabled(_ enabled: Bool) {
        collectionView.isScrollEnabled = enabled
    }
}
