import SwiftUI
import UIKit
import Lottie

// MARK: - Colors

private enum TabBarUIColors {
    static let icon = UIColor { $0.userInterfaceStyle == .dark ? .white : UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) }
    static let selectedIcon = UIColor(RosettaColors.primaryBlue)
    static let text = UIColor { $0.userInterfaceStyle == .dark ? .white : UIColor(red: 0, green: 0, blue: 0, alpha: 0.8) }
    static let selectedText = UIColor(RosettaColors.primaryBlue)
    static let badgeBg = UIColor(red: 1, green: 0.23, blue: 0.19, alpha: 1)
    static let badgeText = UIColor.white
    static let selectionFill = UIColor { traits in
        traits.userInterfaceStyle == .dark
            ? UIColor.white.withAlphaComponent(0.07)
            : UIColor.black.withAlphaComponent(0.12)
    }
}

// MARK: - Gesture (Telegram TabSelectionRecognizer)

private final class TabSelectionGesture: UIGestureRecognizer {
    private(set) var initialLocation: CGPoint = .zero

    override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        delaysTouchesBegan = false
        delaysTouchesEnded = false
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        initialLocation = touches.first?.location(in: view) ?? .zero
        state = .began
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
        state = .changed
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
        state = .ended
    }
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesCancelled(touches, with: event)
        state = .cancelled
    }
}

// MARK: - RosettaTabBarUIView

final class RosettaTabBarUIView: UIView {

    private let tabs = RosettaTab.interactionOrder
    private let innerInset: CGFloat = 4
    private let itemHeight: CGFloat = 56
    private let perItemWidth: CGFloat = 90
    private let lensInset: CGFloat = 4
    private let liftedScale: CGFloat = 1.08

    private var barWidth: CGFloat { perItemWidth * CGFloat(tabs.count) + innerInset * 2 }
    private var barHeight: CGFloat { itemHeight + innerInset * 2 }
    private var itemW: CGFloat { (bounds.width - innerInset * 2) / CGFloat(tabs.count) }

    // MARK: State

    var selectedIndex: Int = 1 {
        didSet {
            if oldValue != selectedIndex {
                updateLensClip(animated: true)
                updateLabelColors()
            }
        }
    }
    var onTabSelected: ((RosettaTab) -> Void)?
    var badgeText: String? { didSet { layoutBadge() } }
    private(set) var isBarVisible: Bool = true

    func setBarVisible(_ visible: Bool, animated: Bool) {
        guard isBarVisible != visible else { return }
        isBarVisible = visible
        isUserInteractionEnabled = visible
        if visible { isHidden = false } // unhide before animation
        let doIt = {
            self.alpha = visible ? 1.0 : 0.0
            self.transform = visible ? .identity : CGAffineTransform(translationX: 0, y: 20)
        }
        let completion = { (_: Bool) in
            if !visible { self.isHidden = true } // hide after animation
        }
        if animated {
            UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.78, initialSpringVelocity: 0, options: [.beginFromCurrentState], animations: doIt, completion: completion)
        } else {
            doIt()
            completion(true)
        }
    }

    private var isDragging = false
    private var didDrag = false // true if .changed fired (not just a tap)
    private var dragLensX: CGFloat = 0
    private var dragStartLensX: CGFloat = 0
    private var hoveredIndex: Int = 1

    // MARK: Subviews — base (unselected, always visible)

    private let glassBackground = TelegramGlassUIView(frame: .zero)
    private let selectionView: UIView = {
        let v = UIView()
        v.backgroundColor = TabBarUIColors.selectionFill
        v.layer.masksToBounds = true
        return v
    }()
    private var iconViews: [LottieAnimationView] = []
    private var labelViews: [UILabel] = []

    // MARK: Subviews — selected overlay (clipped to lens, Telegram LiquidLens parity)

    private let selectedClipView: UIView = {
        let v = UIView()
        v.clipsToBounds = true
        v.isUserInteractionEnabled = false
        return v
    }()
    private let selectedContentView: UIView = {
        let v = UIView()
        v.isUserInteractionEnabled = false
        return v
    }()
    private var selectedIconViews: [LottieAnimationView] = []

    private var badgeBgView: UIView?
    private var badgeLabel: UILabel?

    // Cached RGBA for label interpolation — avoids dynamic UIColor resolution per frame.
    private var textRGBA: (CGFloat, CGFloat, CGFloat, CGFloat) = (1, 1, 1, 1)
    private var selectedTextRGBA: (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 1)

    // MARK: Init

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear
        addSubview(glassBackground)
        addSubview(selectionView)

        for tab in tabs {
            let icon = makeLottie(for: tab); addSubview(icon); iconViews.append(icon)
            let label = makeLabel(for: tab); addSubview(label); labelViews.append(label)
        }

        addSubview(selectedClipView)
        selectedClipView.addSubview(selectedContentView)

        for tab in tabs {
            let icon = makeLottie(for: tab); selectedContentView.addSubview(icon); selectedIconViews.append(icon)
        }

        applyAllColors()
        let g = TabSelectionGesture(target: self, action: #selector(handleGesture(_:)))
        g.delegate = self
        addGestureRecognizer(g)
    }

    required init?(coder: NSCoder) { fatalError() }
    override var intrinsicContentSize: CGSize { CGSize(width: barWidth, height: barHeight) }

    // MARK: Helpers

    private func makeLottie(for tab: RosettaTab) -> LottieAnimationView {
        let v = LottieAnimationView()
        v.contentMode = .scaleAspectFit
        v.isUserInteractionEnabled = false
        v.backgroundBehavior = .pauseAndRestore
        v.animation = LottieAnimationCache.shared.animation(named: tab.animationName)
            ?? LottieAnimation.named(tab.animationName)
        v.loopMode = .playOnce; v.currentProgress = 1.0
        return v
    }

    private func makeLabel(for tab: RosettaTab) -> UILabel {
        let l = UILabel(); l.text = tab.label
        l.font = .systemFont(ofSize: 10, weight: .semibold); l.textAlignment = .center
        return l
    }

    private func setLottieColor(_ view: LottieAnimationView, _ color: UIColor) {
        var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        color.getRed(&r, green: &g, blue: &b, alpha: &a)
        view.setValueProvider(
            ColorValueProvider(LottieColor(r: Double(r), g: Double(g), b: Double(b), a: Double(a))),
            keypath: AnimationKeypath(keypath: "**.Color")
        )
    }

    private func applyAllColors() {
        cacheColorComponents()
        selectionView.backgroundColor = TabBarUIColors.selectionFill
        for i in 0..<tabs.count {
            setLottieColor(iconViews[i], TabBarUIColors.icon)
            setLottieColor(selectedIconViews[i], TabBarUIColors.selectedIcon)
        }
        updateLabelColors()
    }

    private func cacheColorComponents() {
        var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        TabBarUIColors.text.getRed(&r, green: &g, blue: &b, alpha: &a)
        textRGBA = (r, g, b, a)
        TabBarUIColors.selectedText.getRed(&r, green: &g, blue: &b, alpha: &a)
        selectedTextRGBA = (r, g, b, a)
    }

    private func updateLabelColors() {
        let active = selectedIndex
        for i in 0..<tabs.count {
            labelViews[i].textColor = (i == active) ? TabBarUIColors.selectedText : TabBarUIColors.text
        }
    }

    /// Smooth label color interpolation during drag — uses cached RGBA to avoid per-frame UIColor resolution.
    private func updateLabelsForFraction(_ fraction: CGFloat) {
        let clamped = max(0, min(fraction, CGFloat(tabs.count - 1)))
        let lower = Int(clamped)
        let upper = min(lower + 1, tabs.count - 1)
        let t = clamped - CGFloat(lower)
        let (r1, g1, b1, a1) = textRGBA
        let (r2, g2, b2, a2) = selectedTextRGBA
        for i in 0..<tabs.count {
            let s: CGFloat
            if lower == upper { s = (i == lower) ? 1.0 : 0.0 }
            else if i == lower { s = 1.0 - t }
            else if i == upper { s = t }
            else { s = 0.0 }
            labelViews[i].textColor = UIColor(
                red: r1 + (r2 - r1) * s, green: g1 + (g2 - g1) * s,
                blue: b1 + (b2 - b1) * s, alpha: a1 + (a2 - a1) * s
            )
        }
    }

    // MARK: Layout

    override func layoutSubviews() {
        super.layoutSubviews()
        let h = bounds.height
        glassBackground.frame = bounds
        glassBackground.updateGlass()

        let iw = itemW
        for i in 0..<tabs.count {
            let x = innerInset + CGFloat(i) * iw
            let iconSize: CGFloat = 44
            let iconFrame = CGRect(x: x + (iw - iconSize) / 2, y: innerInset + 1, width: iconSize, height: iconSize)
            iconViews[i].frame = iconFrame
            selectedIconViews[i].frame = iconFrame

            let labelFrame = CGRect(x: x, y: h - innerInset - 17, width: iw, height: 14)
            labelViews[i].frame = labelFrame
        }
        // cornerRadius only changes on bounds change, not during drag.
        let lensH = bounds.height - lensInset * 2
        selectionView.layer.cornerRadius = lensH / 2
        selectedClipView.layer.cornerRadius = lensH / 2

        updateLensClip(animated: false)
        layoutBadge()
    }

    // MARK: Lens Clip (Telegram LiquidLens masking)

    private func lensFrame(for index: Int) -> CGRect {
        let lensH = bounds.height - lensInset * 2
        return CGRect(x: innerInset + CGFloat(index) * itemW, y: lensInset, width: itemW, height: lensH)
    }

    private func updateLensClip(animated: Bool) {
        guard bounds.width > 0 else { return }
        let lensH = bounds.height - lensInset * 2

        let target: CGRect
        if isDragging {
            let clampedX = max(lensInset, min(dragLensX, bounds.width - itemW - lensInset))
            target = CGRect(x: clampedX, y: lensInset, width: itemW, height: lensH)
        } else {
            target = lensFrame(for: selectedIndex)
        }

        let apply = {
            self.selectionView.frame = target
            self.selectedClipView.frame = target
            self.selectedContentView.frame = CGRect(
                x: -target.origin.x, y: -target.origin.y,
                width: self.bounds.width, height: self.bounds.height
            )
            if !self.isDragging {
                self.selectionView.transform = .identity
            }
        }
        if animated && !isDragging {
            UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.82,
                           initialSpringVelocity: 0, options: .beginFromCurrentState, animations: apply)
        } else {
            // Disable implicit CoreAnimation during drag — direct property set like Telegram's .immediate
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            apply()
            CATransaction.commit()
        }
    }

    // MARK: Gesture

    @objc private func handleGesture(_ g: TabSelectionGesture) {
        let iw = itemW
        switch g.state {
        case .began:
            isDragging = true
            didDrag = false
            hoveredIndex = max(0, min(Int((g.initialLocation.x - innerInset) / iw), tabs.count - 1))
            dragStartLensX = innerInset + CGFloat(selectedIndex) * iw
            dragLensX = dragStartLensX
            UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 0.8,
                           initialSpringVelocity: 0, options: .beginFromCurrentState) {
                self.selectionView.transform = CGAffineTransform(scaleX: self.liftedScale, y: self.liftedScale)
            }
            updateLensClip(animated: false)

        case .changed:
            didDrag = true
            let loc = g.location(in: self)
            dragLensX = dragStartLensX + (loc.x - g.initialLocation.x)
            hoveredIndex = max(0, min(Int((loc.x - innerInset) / iw), tabs.count - 1))
            let fractionalIdx = max(0, min((dragLensX - innerInset) / iw, CGFloat(tabs.count - 1)))
            updateLabelsForFraction(fractionalIdx)
            updateLensClip(animated: false)

        case .ended:
            isDragging = false
            let targetChanged = hoveredIndex != selectedIndex
            if targetChanged {
                iconViews[hoveredIndex].play(fromProgress: 0, toProgress: 1, loopMode: .playOnce)
                selectedIconViews[hoveredIndex].play(fromProgress: 0, toProgress: 1, loopMode: .playOnce)
                selectedIndex = hoveredIndex // → didSet → updateLensClip(animated: true) + updateLabelColors()
                // Defer SwiftUI state update to next frame — prevents layout pass
                // from blocking the current frame and causing a freeze.
                let tab = tabs[hoveredIndex]
                DispatchQueue.main.async { [weak self] in
                    self?.onTabSelected?(tab)
                }
            } else {
                updateLensClip(animated: true)
                updateLabelColors()
            }

        case .cancelled:
            isDragging = false
            updateLensClip(animated: true)
            updateLabelColors()

        default: break
        }
    }

    // MARK: Badge

    private func layoutBadge() {
        guard let text = badgeText, !text.isEmpty else {
            badgeBgView?.isHidden = true; return
        }
        let chatsIdx = RosettaTab.chats.interactionIndex
        let iw = itemW

        if badgeBgView == nil {
            let bg = UIView(); bg.layer.masksToBounds = true; addSubview(bg); badgeBgView = bg
            let lbl = UILabel(); lbl.font = .systemFont(ofSize: 13, weight: .regular)
            lbl.textAlignment = .center; bg.addSubview(lbl); badgeLabel = lbl
        }
        guard let bg = badgeBgView, let lbl = badgeLabel else { return }
        bg.isHidden = false
        bg.backgroundColor = TabBarUIColors.badgeBg
        lbl.textColor = TabBarUIColors.badgeText
        lbl.text = text; lbl.sizeToFit()

        let textW = lbl.frame.width
        let bgW = text.count == 1 ? 18.0 : max(18.0, textW + 11)
        let bgH: CGFloat = 18
        let itemX = innerInset + CGFloat(chatsIdx) * iw
        bg.frame = CGRect(x: itemX + floor(iw / 2) + 25 - bgW - 1, y: 7, width: bgW, height: bgH)
        bg.layer.cornerRadius = bgH / 2
        lbl.frame = CGRect(x: (bgW - textW) / 2, y: 0.5, width: textW, height: bgH - 1)
        bringSubviewToFront(bg)
    }

    override func traitCollectionDidChange(_ prev: UITraitCollection?) {
        super.traitCollectionDidChange(prev)
        if traitCollection.hasDifferentColorAppearance(comparedTo: prev) {
            applyAllColors()
        }
    }
}

extension RosettaTabBarUIView: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ g: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool { false }
}

// MARK: - SwiftUI Container

struct RosettaTabBarContainer: View {
    let selectedTab: RosettaTab
    var onTabSelected: ((RosettaTab) -> Void)?
    var isVisible: Bool = true
    @State private var cachedBadgeText: String?
    private let barWidth: CGFloat = 90 * 3 + 8

    var body: some View {
        RosettaTabBarBridge(
            selectedTab: selectedTab,
            onTabSelected: onTabSelected,
            badgeText: cachedBadgeText,
            isVisible: isVisible
        )
            .frame(width: barWidth, height: 64)
            .modifier(TabBarShadowModifier())
            .padding(.bottom, 8)
            .onAppear { refreshBadges() }
            .onChange(of: selectedTab) { _, _ in refreshBadges() }
            .overlay {
                BadgeVersionObserver(onVersionChanged: refreshBadges)
                    .frame(width: 0, height: 0).allowsHitTesting(false)
            }
    }

    private func refreshBadges() {
        let unread = DialogRepository.shared.sortedDialogs
            .filter { !$0.isMuted }.reduce(0) { $0 + $1.unreadCount }
        cachedBadgeText = unread <= 0 ? nil : (unread > 999 ? "\(unread / 1000)K" : "\(unread)")
    }
}

private struct RosettaTabBarBridge: UIViewRepresentable {
    let selectedTab: RosettaTab
    var onTabSelected: ((RosettaTab) -> Void)?
    var badgeText: String?
    var isVisible: Bool = true

    func makeUIView(context: Context) -> RosettaTabBarUIView {
        let v = RosettaTabBarUIView(frame: .zero)
        v.selectedIndex = selectedTab.interactionIndex
        v.onTabSelected = onTabSelected; v.badgeText = badgeText
        v.setBarVisible(isVisible, animated: false)
        return v
    }
    func updateUIView(_ v: RosettaTabBarUIView, context: Context) {
        let idx = selectedTab.interactionIndex
        if v.selectedIndex != idx { v.selectedIndex = idx }
        v.onTabSelected = onTabSelected; v.badgeText = badgeText
        if v.isBarVisible != isVisible {
            v.setBarVisible(isVisible, animated: true)
        }
    }
}

private struct TabBarShadowModifier: ViewModifier {
    func body(content: Content) -> some View {
        content.shadow(color: Color.black.opacity(0.12), radius: 20, y: 8)
    }
}

private struct BadgeVersionObserver: View {
    var onVersionChanged: () -> Void
    @State private var refreshTask: Task<Void, Never>?
    @State private var lastUnread: Int = -1
    var body: some View {
        let unread = DialogRepository.shared.dialogs.values
            .reduce(0) { $0 + ($1.isMuted ? 0 : $1.unreadCount) }
        Color.clear
            .onChange(of: unread) { _, v in
                guard v != lastUnread else { return }; lastUnread = v
                refreshTask?.cancel()
                refreshTask = Task { @MainActor in
                    try? await Task.sleep(for: .milliseconds(200))
                    guard !Task.isCancelled else { return }; onVersionChanged()
                }
            }
            .onAppear { lastUnread = unread; onVersionChanged() }
    }
}
