//
//  PlayerDeck.swift
//  react-native-firework-sdk
//

import FireworkVideo
import UIKit

@objc
public enum PlayerDeckSourceType: Int {
    case discover, channel, playlist, playlistGroup, dynamicContent, hashtagPlaylist, sku, singleContent
}

public protocol PlayerDeckViewDelegate: AnyObject {
    func playerDeckDidLoadFeed(_ view: PlayerDeck)
    func playerDeck(_ view: PlayerDeck, didFailToLoadFeed error: VideoFeedError)
}

@objc
public class PlayerDeck: UIView, PlayerDeckFeedViewControllerDelegate, PictureInPictureControllerDelegate {
    @objc public var sourceType: PlayerDeckSourceType = .discover
    @objc public var channel: String = ""
    @objc public var playlist: String = ""
    @objc public var playlistGroup: String = ""
    @objc public var dynamicContentParameters: NSDictionary = NSDictionary()
    @objc public var hashtagFilterExpression: String = ""
    @objc public var productIds: NSArray = NSArray()
    @objc public var contentId: String = ""

    @objc public var playerDeckConfig: PlayerDeckConfiguration? {
        didSet {
            guard let deckView = playerDeckView else { return }
            var contentConfig = deckView.viewConfiguration
            var layout = deckView.layout
            applyDeckConfiguration(playerDeckConfig, to: &contentConfig, layout: &layout)
            if let adBadge = FireworkSDKModule.convertToFWAdBadgeConfiguration(gAdBadgeConfiguration) {
                contentConfig.adBadge = adBadge
            }
            deckView.viewConfiguration = contentConfig
            deckView.layout = layout
        }
    }

    @objc public var playerViewConfig: VideoPlayerConfiguration? {
        didSet {
            guard let deckView = playerDeckView else { return }
            var contentConfig = deckView.viewConfiguration
            if let playerConfig = playerViewConfig,
               let nativePlayerConfig = VideoFeed.convertToVideoPlayerContentConfiguration(playerConfig) {
                contentConfig.playerView = nativePlayerConfig
            }
            VideoFeed.applyVideoLaunchBehavior(to: &contentConfig.playerView)
            deckView.viewConfiguration = contentConfig
            deckView.isPictureInPictureEnabled = playerViewConfig?.enablePictureInPicture ?? false
        }
    }

    @objc var onPlayerDeckLoadFinished: RCTBubblingEventBlock?
    @objc var onPlayerDeckEmpty: RCTBubblingEventBlock?
    @objc var onPlayerDeckDidStartPictureInPicture: RCTBubblingEventBlock?
    @objc var onPlayerDeckDidStopPictureInPicture: RCTBubblingEventBlock?
    @objc var onPlayerDeckGetFeedId: RCTBubblingEventBlock?
    @objc var onPlayerDeckVideosLoaded: RCTBubblingEventBlock?

    public weak var delegate: PlayerDeckViewDelegate?

    private var playerDeckView: PlayerDeckView?
    private var isInPip: Bool = false

    private var source: VideoFeedContentSource {
        switch sourceType {
        case .discover:
            return .discover
        case .channel:
            return .channel(channelID: channel)
        case .playlist:
            return .channelPlaylist(channelID: channel, playlistID: playlist)
        case .playlistGroup:
            return .playlistGroup(groupID: playlistGroup)
        case .dynamicContent:
            let parameters = dynamicContentParameters as? [String: [String]]
            return .dynamicContent(channelID: channel, parameters: parameters ?? [:])
        case .hashtagPlaylist:
            return .hashtagPlaylist(channelID: channel, filterExpression: hashtagFilterExpression)
        case .sku:
            return .skuPlaylist(channelID: channel, productIDs: (productIds as? [String]) ?? [])
        case .singleContent:
            return .singleContent(contentID: contentId)
        }
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        embed()
    }

    private func embed() {
        guard fws_parentViewController != nil else { return }
        guard self.playerDeckView == nil else { return }

        let deckLayout = PlayerDeckLayout()
        let deckView = PlayerDeckView(layout: deckLayout, source: source)

        onPlayerDeckGetFeedId?(["feedId": deckView.feedID])

        var contentConfig = deckView.viewConfiguration
        var layout = deckView.layout

        applyDeckConfiguration(playerDeckConfig, to: &contentConfig, layout: &layout)

        if let playerConfig = playerViewConfig,
           let nativePlayerConfig = VideoFeed.convertToVideoPlayerContentConfiguration(playerConfig) {
            contentConfig.playerView = nativePlayerConfig
        }
        // Applied here too so the full screen player still follows the SDK init
        // launch behavior when no videoPlayerConfiguration is provided.
        VideoFeed.applyVideoLaunchBehavior(to: &contentConfig.playerView)

        if let adBadge = FireworkSDKModule.convertToFWAdBadgeConfiguration(gAdBadgeConfiguration) {
            contentConfig.adBadge = adBadge
        }

        deckView.viewConfiguration = contentConfig
        deckView.layout = layout
        deckView.isPictureInPictureEnabled = playerViewConfig?.enablePictureInPicture ?? false
        deckView.delegate = self
        deckView.pictureInPictureDelegate = self
        // Set before the deck is added to the hierarchy (and starts loading) so
        // the initial page is delivered too. The SDK calls this for every
        // loaded batch, initial page and each load-more page, with only that
        // batch's videos.
        deckView.onVideosLoaded = { [weak self] info in
            self?.onPlayerDeckVideosLoaded?(["videos": info.videos.map { $0.jsObject }])
        }

        self.playerDeckView = deckView

        deckView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(deckView)
        NSLayoutConstraint.activate([
            deckView.topAnchor.constraint(equalTo: self.topAnchor),
            deckView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            deckView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            deckView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])
    }

    public override func removeFromSuperview() {
        super.removeFromSuperview()
        guard let deckView = self.playerDeckView else { return }
        if !isInPip {
            deckView.isPictureInPictureEnabled = false
        }
        deckView.removeFromSuperview()
        self.playerDeckView = nil
    }

    @objc
    public func refresh() {
        playerDeckView?.refresh()
    }

    // MARK: - PlayerDeckFeedViewControllerDelegate

    public func playerDeckFeedDidLoadFeed(_ viewController: PlayerDeckFeedViewController) {
        delegate?.playerDeckDidLoadFeed(self)
    }

    public func playerDeckFeed(
        _ viewController: PlayerDeckFeedViewController,
        didFailToLoadFeed error: VideoFeedError
    ) {
        delegate?.playerDeck(self, didFailToLoadFeed: error)
    }

    // MARK: - PictureInPictureControllerDelegate

    public func pictureInPictureControllerWillStartPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {}

    public func pictureInPictureControllerDidStartPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
        self.onPlayerDeckDidStartPictureInPicture?(nil)
        isInPip = true
    }

    public func pictureInPictureController(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController,
        failedToStartPictureInPictureWithError error: Error?
    ) {}

    public func pictureInPictureControllerWillStopPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {}

    public func pictureInPictureControllerDidStopPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
        self.onPlayerDeckDidStopPictureInPicture?(nil)
        isInPip = false
    }

    public func pictureInPictureController(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController,
        restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
    ) {
        completionHandler(true)
    }
}

// MARK: - Configuration Helpers

extension PlayerDeck {
    // swiftlint:disable:next cyclomatic_complexity function_body_length
    fileprivate func applyDeckConfiguration(
        _ config: PlayerDeckConfiguration?,
        to contentConfig: inout PlayerDeckContentConfiguration,
        layout: inout any PlayerDeckLayoutProtocol
    ) {
        contentConfig.useSafeAreaViewport = false
        // Pin the autoplay trigger threshold; the iOS SDK default has changed.
        // Set before the nil guard so it also applies when no deck config is
        // provided from JS.
        contentConfig.itemView.autoplay.triggerVisibilityPercentage = 100
        // The first display mute state comes from the SDK init options, so set it
        // before the nil guard as well; the onFirstDisplayMuteState prop below
        // still overrides it when JS provides one.
        applyLaunchBehaviorMuteState(to: &contentConfig)

        guard let config = config else { return }

        if let itemSpacing = config.itemSpacing {
            layout.itemSpacing = itemSpacing
        }
        if let contentPadding = config.contentPadding {
            if let top = contentPadding.top { layout.contentInsets.top = top }
            if let right = contentPadding.right { layout.contentInsets.right = right }
            if let bottom = contentPadding.bottom { layout.contentInsets.bottom = bottom }
            if let left = contentPadding.left { layout.contentInsets.left = left }
        }
        if let backgroundColor = config.backgroundColor, let color = backgroundColor.uicolor() {
            contentConfig.backgroundColor = color
        }
        if let cornerRadius = config.cornerRadius {
            contentConfig.itemView.cornerRadius = cornerRadius
        }
        if let autoplay = config.autoplay {
            if let isEnabled = autoplay.isEnabled {
                contentConfig.itemView.autoplay.isEnabled = isEnabled
            }
        }
        if let playIcon = config.playIcon {
            playIcon.apply(to: &contentConfig.itemView.playIcon)
        }
        if let showAdBadge = config.showAdBadge {
            contentConfig.itemView.sponsored.isHidden = !showAdBadge
        }
        if let shareButton = config.shareButton {
            if let hidden = shareButton.hidden {
                contentConfig.itemView.shareButton.isHidden = hidden
            }
        }
        if let muteButton = config.muteButton {
            if let hidden = muteButton.hidden {
                contentConfig.itemView.muteButton.isHidden = hidden
            }
        }
        if let fullScreen = config.fullScreen {
            if let isEnabled = fullScreen.isEnabled {
                contentConfig.itemView.playerFullScreen.isEnabled = isEnabled
            }
        }
        if let muteState = config.onFirstDisplayMuteState {
            switch muteState {
            case "unmuted":
                contentConfig.itemView.onFirstDisplayMuteState = .unmuted
            default:
                contentConfig.itemView.onFirstDisplayMuteState = .default
            }
        }
        if let subtitle = config.subtitleConfiguration {
            if let swap = subtitle.swapMuteAndSubtitleButtons {
                contentConfig.itemView.subtitleConfiguration.subtitleButtonPosition
                    .swapMuteAndSubtitleButtons = swap
            }
            if let textColor = subtitle.textColor, let color = textColor.uicolor() {
                contentConfig.itemView.subtitleConfiguration.textColor = color
            }
            if let bgColor = subtitle.backgroundColor, let color = bgColor.uicolor() {
                contentConfig.itemView.subtitleConfiguration.backgroundColor = color
            }
        }
    }

    fileprivate func applyLaunchBehaviorMuteState(to contentConfig: inout PlayerDeckContentConfiguration) {
        if let playerDeckBehavior = gVideoLaunchBehavior?.playerDeckBehavior() {
            contentConfig.itemView.onFirstDisplayMuteState = playerDeckBehavior
        } else {
            contentConfig.itemView.onFirstDisplayMuteState = .unmuted
        }
    }
}
