//
// Copyright (c) Double Symmetry GmbH
// Commercial use requires a license. See https://rntp.dev/pricing
//

import CarPlay
import UIKit

@objc(RNTPCarPlaySceneDelegate)
class RNTPCarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {

    private var interfaceController: CPInterfaceController?
    private let imageCache = NSCache<NSString, UIImage>()
    private var notificationObserver: NSObjectProtocol?
    private var nowPlayingObserver: NSObjectProtocol?
    /// All track list items keyed by mediaId, for updating isPlaying state.
    private var listItemsByMediaId: [String: [CPListItem]] = [:]

    // MARK: - CPTemplateApplicationSceneDelegate

    func templateApplicationScene(
        _ templateApplicationScene: CPTemplateApplicationScene,
        didConnect interfaceController: CPInterfaceController
    ) {
        self.interfaceController = interfaceController
        rebuildTemplates()

        notificationObserver = NotificationCenter.default.addObserver(
            forName: BrowseTreeStore.didChangeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.rebuildTemplates()
        }

        nowPlayingObserver = NotificationCenter.default.addObserver(
            forName: BrowseTreeStore.nowPlayingChangedNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.updateNowPlayingState()
        }
    }

    func templateApplicationScene(
        _ templateApplicationScene: CPTemplateApplicationScene,
        didDisconnect interfaceController: CPInterfaceController
    ) {
        if let observer = notificationObserver {
            NotificationCenter.default.removeObserver(observer)
            notificationObserver = nil
        }
        if let observer = nowPlayingObserver {
            NotificationCenter.default.removeObserver(observer)
            nowPlayingObserver = nil
        }
        listItemsByMediaId = [:]
        self.interfaceController = nil
    }

    // MARK: - Template Construction

    private func rebuildTemplates() {
        guard let interfaceController = interfaceController else { return }
        listItemsByMediaId = [:]

        let allCategories = BrowseTreeStore.shared.categories
        // Filter out empty categories
        let categories = allCategories.filter { cat in
            guard let items = cat["items"] as? [[String: Any]] else { return false }
            return !items.isEmpty
        }

        guard !categories.isEmpty else {
            interfaceController.setRootTemplate(CPNowPlayingTemplate.shared, animated: true, completion: nil)
            return
        }

        if categories.count == 1 {
            let list = buildListTemplate(for: categories[0], categoryIndex: 0)
            interfaceController.setRootTemplate(list, animated: true, completion: nil)
            return
        }

        let maxTabs = min(4, CPTabBarTemplate.maximumTabCount)

        if categories.count <= maxTabs {
            let tabs = categories.enumerated().map { (i, cat) in
                buildListTemplate(for: cat, categoryIndex: i)
            }
            let tabBar = CPTabBarTemplate(templates: tabs)
            interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
        } else {
            // First (maxTabs - 1) as direct tabs, rest under "More"
            let directCount = maxTabs - 1
            var tabs: [CPListTemplate] = []
            for i in 0..<directCount {
                tabs.append(buildListTemplate(for: categories[i], categoryIndex: i))
            }
            let moreTab = buildMoreTemplate(categories: Array(categories.dropFirst(directCount)),
                                            startingCategoryIndex: directCount)
            tabs.append(moreTab)
            let tabBar = CPTabBarTemplate(templates: tabs)
            interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
        }
    }

    // MARK: - List Template Builders

    private func buildListTemplate(for category: [String: Any], categoryIndex: Int) -> CPListTemplate {
        let title = category["title"] as? String ?? "Untitled"
        let items = category["items"] as? [[String: Any]] ?? []

        let maxItems = CPListTemplate.maximumItemCount
        let truncated = maxItems > 0 ? Array(items.prefix(maxItems)) : items

        let listItems: [CPListItem] = truncated.enumerated().map { (itemIndex, item) in
            buildListItem(item: item, parentItems: items, itemIndex: itemIndex)
        }

        let section = CPListSection(items: listItems)
        let template = CPListTemplate(title: title, sections: [section])
        template.tabTitle = title
        return template
    }

    private func buildMoreTemplate(categories: [[String: Any]], startingCategoryIndex: Int) -> CPListTemplate {
        let listItems: [CPListItem] = categories.enumerated().map { (offset, cat) in
            let catTitle = cat["title"] as? String ?? "Untitled"
            let item = CPListItem(text: catTitle, detailText: nil)
            let catIndex = startingCategoryIndex + offset
            item.userInfo = ["type": "category", "categoryIndex": catIndex]
            item.handler = { [weak self] _, completion in
                self?.handleMoreCategorySelected(category: cat, categoryIndex: catIndex)
                completion()
            }
            return item
        }

        let section = CPListSection(items: listItems)
        let template = CPListTemplate(title: "More", sections: [section])
        template.tabTitle = "More"
        template.tabImage = UIImage(systemName: "ellipsis.circle")
        return template
    }

    private func handleMoreCategorySelected(category: [String: Any], categoryIndex: Int) {
        let detail = buildListTemplate(for: category, categoryIndex: categoryIndex)
        interfaceController?.pushTemplate(detail, animated: true, completion: nil)
    }

    // MARK: - List Item Builder

    private func buildListItem(item: [String: Any], parentItems: [[String: Any]], itemIndex: Int) -> CPListItem {
        let title = item["title"] as? String ?? "Untitled"
        let artist = item["artist"] as? String
        let listItem = CPListItem(text: title, detailText: artist)

        let hasUrl = item["url"] != nil
        let children = item["children"] as? [[String: Any]]
        let isBrowsable = children != nil && !hasUrl

        if isBrowsable {
            listItem.accessoryType = .disclosureIndicator
            listItem.handler = { [weak self] _, completion in
                self?.handleBrowsableSelected(item: item)
                completion()
            }
        } else {
            listItem.handler = { [weak self] _, completion in
                self?.handlePlayableSelected(parentItems: parentItems, itemIndex: itemIndex)
                completion()
            }
        }

        // Track by mediaId for now-playing state updates
        if let mediaId = item["mediaId"] as? String {
            listItemsByMediaId[mediaId, default: []].append(listItem)
        }

        // Mark as playing if this is the current item
        if let mediaId = item["mediaId"] as? String,
           mediaId == BrowseTreeStore.shared.currentMediaId {
            listItem.isPlaying = true
        }

        // Load artwork async
        if let artworkUrlString = item["artworkUrl"] as? String,
           let artworkUrl = URL(string: artworkUrlString) {
            loadImage(url: artworkUrl) { [weak listItem] image in
                listItem?.setImage(image)
            }
        }

        return listItem
    }

    private func handleBrowsableSelected(item: [String: Any]) {
        let title = item["title"] as? String ?? "Untitled"
        let children = item["children"] as? [[String: Any]] ?? []

        let maxItems = CPListTemplate.maximumItemCount
        let truncated = maxItems > 0 ? Array(children.prefix(maxItems)) : children

        let listItems = truncated.enumerated().map { (idx, child) in
            buildListItem(item: child, parentItems: children, itemIndex: idx)
        }

        let section = CPListSection(items: listItems)
        let template = CPListTemplate(title: title, sections: [section])
        interfaceController?.pushTemplate(template, animated: true, completion: nil)
    }

    // MARK: - Now Playing State

    private func updateNowPlayingState() {
        let currentMediaId = BrowseTreeStore.shared.currentMediaId
        for (mediaId, items) in listItemsByMediaId {
            let isPlaying = mediaId == currentMediaId
            for item in items {
                item.isPlaying = isPlaying
            }
        }
    }

    // MARK: - Playback

    private func handlePlayableSelected(parentItems: [[String: Any]], itemIndex: Int) {
        guard let player = BrowseTreeStore.shared.player else { return }

        // Collect only playable siblings (items with a url)
        let playableItems = parentItems.filter { $0["url"] != nil }
        let mediaItems = playableItems.compactMap { MediaItem(data: $0) }
        guard !mediaItems.isEmpty else { return }

        // Find the index of the selected item among playable siblings
        let selectedMediaId = parentItems[itemIndex]["mediaId"] as? String
        let playableIndex = playableItems.firstIndex { ($0["mediaId"] as? String) == selectedMediaId } ?? 0

        player.clear()
        player.add(items: mediaItems)
        if playableIndex > 0 {
            player.skipTo(index: playableIndex)
        }
        player.play()
    }

    // MARK: - Artwork

    private func loadImage(url: URL, completion: @escaping (UIImage) -> Void) {
        let key = url.absoluteString as NSString
        if let cached = imageCache.object(forKey: key) {
            completion(cached)
            return
        }

        URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
            guard let data = data, let image = UIImage(data: data) else { return }

            // Scale to CarPlay max image size
            let maxSize = CPListItem.maximumImageSize
            let scaled = Self.scaleImage(image, to: maxSize)

            self?.imageCache.setObject(scaled, forKey: key)
            DispatchQueue.main.async {
                completion(scaled)
            }
        }.resume()
    }

    private static func scaleImage(_ image: UIImage, to maxSize: CGSize) -> UIImage {
        let widthRatio = maxSize.width / image.size.width
        let heightRatio = maxSize.height / image.size.height
        let ratio = min(widthRatio, heightRatio, 1.0) // Don't upscale
        if ratio >= 1.0 { return image }

        let newSize = CGSize(width: image.size.width * ratio, height: image.size.height * ratio)
        let renderer = UIGraphicsImageRenderer(size: newSize)
        return renderer.image { _ in
            image.draw(in: CGRect(origin: .zero, size: newSize))
        }
    }
}
