import SwiftUI
import UIKit

// MARK: - Tab

enum RosettaTab: String, CaseIterable, Sendable {
    case chats
    case calls
    case settings

    static let interactionOrder: [RosettaTab] = [.calls, .chats, .settings]

    var label: String {
        switch self {
        case .chats: return "Chats"
        case .calls: return "Calls"
        case .settings: return "Settings"
        }
    }

    var icon: String {
        switch self {
        case .chats: return "bubble.left.and.bubble.right"
        case .calls: return "phone"
        case .settings: return "gearshape"
        }
    }

    var selectedIcon: String {
        switch self {
        case .chats: return "bubble.left.and.bubble.right.fill"
        case .calls: return "phone.fill"
        case .settings: return "gearshape.fill"
        }
    }

    var interactionIndex: Int {
        Self.interactionOrder.firstIndex(of: self) ?? 0
    }
}

// MARK: - Tab Badge

struct TabBadge {
    let tab: RosettaTab
    let text: String
}

struct TabBarSwipeState {
    let fromTab: RosettaTab
    let hoveredTab: RosettaTab
    let fractionalIndex: CGFloat
}

// MARK: - Tab Bar Colors

private enum TabBarColors {
    static let pillBackground = RosettaColors.adaptive(
        light: Color(hex: 0xF2F2F7),
        dark: Color(hex: 0x2C2C2E)
    )
    static let selectionBackground = RosettaColors.adaptive(
        light: Color.white,
        dark: Color(hex: 0x3A3A3C)
    )
    static let selectedTint = Color(hex: 0x008BFF)
    static let unselectedTint = RosettaColors.adaptive(
        light: Color(hex: 0x3C3C43).opacity(0.6),
        dark: Color.white
    )
    static let pillBorder = RosettaColors.adaptive(
        light: Color.black.opacity(0.08),
        dark: Color.white.opacity(0.08)
    )
}

// MARK: - Preference Keys

private struct TabWidthPreferenceKey: PreferenceKey {
    static var defaultValue: [Int: CGFloat] = [:]
    static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
        value.merge(nextValue()) { $1 }
    }
}

private struct TabOriginPreferenceKey: PreferenceKey {
    static var defaultValue: [Int: CGFloat] = [:]
    static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
        value.merge(nextValue()) { $1 }
    }
}

// MARK: - RosettaTabBar

struct RosettaTabBar: View {
    let selectedTab: RosettaTab
    var onTabSelected: ((RosettaTab) -> Void)?
    var onSwipeStateChanged: ((TabBarSwipeState?) -> Void)?

    private let allTabs = RosettaTab.interactionOrder
    private let tabCount = RosettaTab.interactionOrder.count

    // Drag state
    @State private var isDragging = false
    @State private var dragFractional: CGFloat = 0
    @State private var dragStartIndex: CGFloat = 0

    // Measured tab geometry
    @State private var tabWidths: [Int: CGFloat] = [:]
    @State private var tabOrigins: [Int: CGFloat] = [:]

    /// Cached badge text to avoid reading DialogRepository inside body
    /// (which creates @Observable tracking and causes re-render storms during drag).
    @State private var cachedBadgeText: String?

    private var effectiveFractional: CGFloat {
        isDragging ? dragFractional : CGFloat(selectedTab.interactionIndex)
    }

    var body: some View {
        // Single pill with all tabs — same structure as iOS 26 system TabView
        HStack(spacing: 0) {
            ForEach(Array(allTabs.enumerated()), id: \.element) { index, tab in
                tabContent(tab: tab, index: index)
                    .background(
                        GeometryReader { geo in
                            Color.clear
                                .preference(
                                    key: TabWidthPreferenceKey.self,
                                    value: [index: geo.size.width]
                                )
                                .preference(
                                    key: TabOriginPreferenceKey.self,
                                    value: [index: geo.frame(in: .named("tabBar")).minX]
                                )
                        }
                    )
            }
        }
        .padding(4)
        .coordinateSpace(name: "tabBar")
        .onPreferenceChange(TabWidthPreferenceKey.self) { tabWidths = $0 }
        .onPreferenceChange(TabOriginPreferenceKey.self) { tabOrigins = $0 }
        .background(alignment: .leading) {
            selectionIndicator
        }
        .background {
            // TelegramGlassCapsule handles both iOS 26+ (UIGlassEffect)
            // and iOS < 26 (CABackdropLayer), with isUserInteractionEnabled = false.
            TelegramGlassCapsule()
        }
        .contentShape(Capsule())
        .gesture(dragGesture)
        .modifier(TabBarShadowModifier())
        .padding(.horizontal, 25)
        .padding(.top, 16)
        .padding(.bottom, 12)
        .onAppear { Task { @MainActor in refreshBadges() } }
        .onChange(of: selectedTab) { _, _ in Task { @MainActor in refreshBadges() } }
        // Observation-isolated badge refresh: checks dialogsVersion on every
        // DialogRepository mutation. Only calls refreshBadges() when version changes.
        .overlay {
            BadgeVersionObserver(onVersionChanged: refreshBadges)
                .frame(width: 0, height: 0)
                .allowsHitTesting(false)
        }
    }

    /// Reads DialogRepository outside the body's observation scope.
    private func refreshBadges() {
        let repo = DialogRepository.shared
        let unread = repo.sortedDialogs
            .filter { !$0.isMuted }
            .reduce(0) { $0 + $1.unreadCount }
        if unread <= 0 {
            cachedBadgeText = nil
        } else {
            cachedBadgeText = unread > 999 ? "\(unread / 1000)K" : "\(unread)"
        }
    }

    // MARK: - Selection Indicator

    @ViewBuilder
    private var selectionIndicator: some View {
        let frac = effectiveFractional
        let nearestIdx = Int(frac.rounded()).clamped(to: 0...(tabCount - 1))
        let width = tabWidths[nearestIdx] ?? 80
        let xOffset = interpolatedOrigin(for: frac)

        Group {
            if #available(iOS 26.0, *) {
                // iOS 26+ — native liquid glass
                Capsule().fill(.clear)
                    .glassEffect(.regular, in: .capsule)
                    .allowsHitTesting(false)
                    .frame(width: width)
                    .offset(x: xOffset)
            } else {
                // iOS < 26 — thin frosted glass
                // +2 centers the narrowed (width-4) pill within the tab
                Capsule().fill(.thinMaterial)
                    .frame(width: width - 4)
                    .padding(.vertical, 4)
                    .offset(x: xOffset + 2)
            }
        }
        .animation(
            isDragging ? nil : .spring(response: 0.34, dampingFraction: 0.82),
            value: frac
        )
    }

    private func interpolatedOrigin(for fractional: CGFloat) -> CGFloat {
        let lower = Int(fractional).clamped(to: 0...(tabCount - 1))
        let upper = (lower + 1).clamped(to: 0...(tabCount - 1))
        let t = fractional - CGFloat(lower)
        let lowerX = tabOrigins[lower] ?? 0
        let upperX = tabOrigins[upper] ?? lowerX
        return lowerX + (upperX - lowerX) * t
    }

    // MARK: - Drag Gesture

    private var dragGesture: some Gesture {
        DragGesture(minimumDistance: 8)
            .onChanged { value in
                if !isDragging {
                    isDragging = true
                    dragStartIndex = CGFloat(selectedTab.interactionIndex)
                }

                let avgTabWidth = totalTabWidth / CGFloat(tabCount)
                guard avgTabWidth > 0 else { return }
                let delta = value.translation.width / avgTabWidth
                let newFrac = (dragStartIndex - delta)
                    .clamped(to: 0...CGFloat(tabCount - 1))

                dragFractional = newFrac

                let nearestIdx = Int(newFrac.rounded()).clamped(to: 0...(tabCount - 1))
                onSwipeStateChanged?(TabBarSwipeState(
                    fromTab: selectedTab,
                    hoveredTab: allTabs[nearestIdx],
                    fractionalIndex: newFrac
                ))
            }
            .onEnded { value in
                let avgTabWidth = totalTabWidth / CGFloat(tabCount)
                let velocity = avgTabWidth > 0 ? value.predictedEndTranslation.width / avgTabWidth : 0
                let projected = dragFractional - velocity * 0.15
                let snappedIdx = Int(projected.rounded()).clamped(to: 0...(tabCount - 1))
                let targetTab = allTabs[snappedIdx]

                isDragging = false
                dragFractional = CGFloat(snappedIdx)

                UIImpactFeedbackGenerator(style: .light).impactOccurred()
                onTabSelected?(targetTab)
                onSwipeStateChanged?(nil)
            }
    }

    private var totalTabWidth: CGFloat {
        tabWidths.values.reduce(0, +)
    }

    // MARK: - Tab Content

    private func tabContent(tab: RosettaTab, index: Int) -> some View {
        let frac = effectiveFractional
        let distance = abs(frac - CGFloat(index))
        let blend = (1 - distance).clamped(to: 0...1)
        let tint = tintColor(blend: blend)
        let isEffectivelySelected = blend > 0.5
        let badge: String? = (tab == .chats) ? cachedBadgeText : nil

        return Button {
            guard !isDragging else { return }
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
            onTabSelected?(tab)
        } label: {
            VStack(spacing: 2) {
                ZStack(alignment: .topTrailing) {
                    Image(systemName: isEffectivelySelected ? tab.selectedIcon : tab.icon)
                        .font(.system(size: 22, weight: .regular))
                        .foregroundStyle(tint)
                        .frame(height: 28)

                    if let badge {
                        Text(badge)
                            .font(.system(size: 10, weight: .bold))
                            .foregroundStyle(.white)
                            .padding(.horizontal, badge.count > 2 ? 4 : 0)
                            .frame(minWidth: 18, minHeight: 18)
                            .background(Capsule().fill(RosettaColors.error))
                            .offset(x: 10, y: -4)
                    }
                }

                Text(tab.label)
                    .font(.system(size: 10, weight: isEffectivelySelected ? .bold : .medium))
                    .foregroundStyle(tint)
            }
            .frame(minWidth: 66, maxWidth: .infinity)
            .padding(.vertical, 6)
        }
        .buttonStyle(.plain)
        .accessibilityLabel(tab.label)
        .accessibilityAddTraits(isEffectivelySelected ? .isSelected : [])
    }

    // MARK: - Color Interpolation

    @Environment(\.colorScheme) private var colorScheme

    private static let selectedRGBA: (CGFloat, CGFloat, CGFloat, CGFloat) = {
        var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
        UIColor(TabBarColors.selectedTint).getRed(&r, green: &g, blue: &b, alpha: &a)
        return (r, g, b, a)
    }()

    private func tintColor(blend: CGFloat) -> Color {
        let t = blend.clamped(to: 0...1)
        // Unselected tint depends on theme — compute at render time
        let isDark = colorScheme == .dark
        let fr: CGFloat = isDark ? 1.0 : 0.235
        let fg: CGFloat = isDark ? 1.0 : 0.235
        let fb: CGFloat = isDark ? 1.0 : 0.263
        let fa: CGFloat = isDark ? 1.0 : 0.6
        let (tr, tg, tb, ta) = Self.selectedRGBA
        return Color(
            red: fr + (tr - fr) * t,
            green: fg + (tg - fg) * t,
            blue: fb + (tb - fb) * t,
            opacity: fa + (ta - fa) * t
        )
    }
}

// MARK: - Shadow (iOS < 26 only)

/// Glass has built-in depth on iOS 26+, so shadow is only needed on older versions.
private struct TabBarShadowModifier: ViewModifier {
    func body(content: Content) -> some View {
        if #available(iOS 26.0, *) {
            content
        } else {
            content
                .shadow(color: Color.black.opacity(0.12), radius: 20, y: 8)
        }
    }
}

// MARK: - Comparable Clamping

private extension Comparable {
    func clamped(to range: ClosedRange<Self>) -> Self {
        min(max(self, range.lowerBound), range.upperBound)
    }
}

// MARK: - Preview

// MARK: - Badge Version Observer (observation-isolated)

/// Observes DialogRepository in its own scope, debounced badge refresh.
/// Body re-evaluates on any dialog mutation (observation tracking via `dialogs` read).
/// Debounces at 500ms to avoid CPU spikes from rapid mutations.
private struct BadgeVersionObserver: View {
    var onVersionChanged: () -> Void
    @State private var refreshTask: Task<Void, Never>?
    @State private var lastUnread: Int = -1

    var body: some View {
        // O(n) reduce — but only on THIS view's body (isolated scope).
        // Not called during RosettaTabBar drag/animation.
        let unread = DialogRepository.shared.dialogs.values
            .reduce(0) { $0 + ($1.isMuted ? 0 : $1.unreadCount) }
        Color.clear
            .onChange(of: unread) { _, newValue in
                guard newValue != lastUnread else { return }
                lastUnread = newValue
                // Debounce to avoid rapid refreshes during sync
                refreshTask?.cancel()
                refreshTask = Task { @MainActor in
                    try? await Task.sleep(for: .milliseconds(200))
                    guard !Task.isCancelled else { return }
                    onVersionChanged()
                }
            }
            .onAppear {
                lastUnread = unread
                onVersionChanged()
            }
    }
}

#Preview {
    ZStack(alignment: .bottom) {
        Color.black.ignoresSafeArea()
        RosettaTabBar(selectedTab: .chats)
    }
}
