import SwiftUI

/// Main container view with tab-based navigation.
struct MainTabView: View {
    var onLogout: (() -> Void)?
    /// Always start on Chats tab after login / account switch.
    /// Using @State (not @SceneStorage) ensures the tab resets to .chats
    /// when MainTabView is recreated after an account switch or unlock.
    @State private var selectedTab: RosettaTab = .chats
    @State private var isChatSearchActive = false
    @State private var isChatListDetailPresented = false
    @State private var isSettingsEditPresented = false
    @State private var isSettingsDetailPresented = false
    @StateObject private var callManager = CallManager.shared

    // Add Account — presented as fullScreenCover so Settings stays alive.
    // Using optional AuthScreen as the item ensures the correct screen is
    // passed directly to the content closure (no stale capture).
    @State private var addAccountScreen: AuthScreen?
    /// All tabs are pre-activated so that switching only changes the offset,
    /// not the view structure. Creating a NavigationStack mid-animation causes
    /// "Update NavigationRequestObserver tried to update multiple times per frame" → freeze.
    @State private var activatedTabs: Set<RosettaTab> = Set(RosettaTab.interactionOrder)
    /// When non-nil, the tab bar is being dragged and the pager follows interactively.
    @State private var dragFractionalIndex: CGFloat?

    /// Local handler for Add Account — triggers fullScreenCover instead of app-level navigation.
    private var handleAddAccount: (AuthScreen) -> Void {
        { screen in
            addAccountScreen = screen
        }
    }

    var body: some View {
        #if DEBUG
        let _ = PerformanceLogger.shared.track("mainTab.bodyEval")
        #endif
        ZStack {
            legacyTabView
            .fullScreenCover(item: $addAccountScreen) { screen in
                AuthCoordinator(
                    onAuthComplete: {
                        addAccountScreen = nil
                        // New account created — end session and go to unlock for the new account
                        SessionManager.shared.endSession()
                        onLogout?()
                    },
                    onBackToUnlock: {
                        addAccountScreen = nil
                    },
                    initialScreen: screen
                )
            }
            // Minimized call bar pushes tab content down.
            // Applied HERE (not on outer ZStack) so the full-screen overlay
            // is NOT affected — prevents jerk when overlay transitions out.
            .safeAreaInset(edge: .top, spacing: 0) {
                // No minimized bar for .incoming — CallKit handles incoming UI.
                // Bar appears only after accept (keyExchange, webRtcExchange, active).
                if callManager.uiState.isVisible && callManager.uiState.isMinimized
                    && callManager.uiState.phase != .incoming {
                    MinimizedCallBar(callManager: callManager)
                        .transition(.move(edge: .top).combined(with: .opacity))
                } else {
                    EmptyView()
                }
            }

            // Full-screen device verification overlay (observation-isolated).
            // Covers nav bar, search bar, and tab bar — desktop parity.
            DeviceConfirmOverlay()

            // PERF: observation-isolated unread counter.
            // Reads DialogRepository in its own scope — MainTabView.body
            // never observes the dialogs dictionary directly.
            UnreadCountObserver(count: $cachedUnreadCount)

            // Full-screen call overlay — OUTSIDE .safeAreaInset scope.
            // Animation driven by withAnimation in CallManager methods —
            // no .animation() modifiers here to avoid NavigationStack conflicts.
            // Custom call overlay — only AFTER accept (not for .incoming).
            // Telegram parity: CallKit handles incoming UI, custom UI handles active call.
            Group {
                if callManager.uiState.isFullScreenVisible
                    && callManager.uiState.phase != .incoming {
                    ActiveCallOverlayView(callManager: callManager)
                        // Asymmetric: slide-in from bottom on appear,
                        // fade-only on removal to avoid conflict with dragOffset
                        // (swipe already moved the view off-screen).
                        .transition(.asymmetric(
                            insertion: .move(edge: .bottom).combined(with: .opacity),
                            removal: .opacity
                        ))
                }
            }
            .zIndex(10)
        }
        // Switch to Chats tab when user taps a push notification.
        // Without this, the navigation happens in the Chats NavigationStack
        // but the user stays on whatever tab they were on (e.g., Settings).
        .onReceive(NotificationCenter.default.publisher(for: .openChatFromNotification)) { _ in
            if selectedTab != .chats {
                selectedTab = .chats
            }
        }
    }

    // MARK: - Tab View (custom RosettaTabBar with pager)

    private var legacyTabView: some View {
        ZStack(alignment: .bottom) {
            RosettaColors.Adaptive.background
                .ignoresSafeArea()

            GeometryReader { geometry in
                tabPager(availableSize: geometry.size)
            }
            .ignoresSafeArea()

            RosettaTabBarContainer(
                selectedTab: selectedTab,
                onTabSelected: { tab in
                    selectedTab = tab
                },
                isVisible: !isChatSearchActive && !isAnyChatDetailPresented
                    && !isSettingsEditPresented && !isSettingsDetailPresented
            )
            .ignoresSafeArea(.keyboard)
        }
        .ignoresSafeArea(.keyboard)
        .onChange(of: isChatSearchActive) { _, isActive in
            if isActive {
                dragFractionalIndex = nil
            }
        }
    }

    private var currentPageIndex: CGFloat {
        CGFloat(selectedTab.interactionIndex)
    }

    // MARK: - Tab Pager (Telegram Parity)
    //
    // Telegram TabBarController animation:
    //   New tab: alpha 0→1 (0.1s) + scale 0.998→1.0 (0.15s, delay 0.1s, spring)
    //   Old tab: just removed (no fade-out)
    //
    // zIndex ensures selected tab is always on top, so only the fade-IN
    // is visible — the old tab's opacity change is hidden underneath.

    @ViewBuilder
    private func tabPager(availableSize: CGSize) -> some View {
        let width = max(1, availableSize.width)

        ZStack {
            ForEach(RosettaTab.interactionOrder, id: \.self) { tab in
                let isSelected = tab == selectedTab
                tabView(for: tab)
                    .frame(width: width, height: availableSize.height)
                    .zIndex(isSelected ? 1 : 0)
                    .opacity(isSelected ? 1 : 0)
                    .environment(\.telegramGlassActive, isSelected)
                    .animation(.easeOut(duration: 0.12), value: selectedTab)
                    .allowsHitTesting(isSelected)
            }
        }
        .clipped()
    }

    @ViewBuilder
    private func tabView(for tab: RosettaTab) -> some View {
        if activatedTabs.contains(tab) {
            switch tab {
            case .chats:
                ChatListUIKitView(
                    isSearchActive: $isChatSearchActive,
                    isDetailPresented: $isChatListDetailPresented
                )
                .callBarSafeAreaInset(callBarTopPadding)
            case .calls:
                CallsView()
                    .callBarSafeAreaInset(callBarTopPadding)
            case .settings:
                SettingsContainerView(onLogout: onLogout, onAddAccount: handleAddAccount, isEditingProfile: $isSettingsEditPresented, isDetailPresented: $isSettingsDetailPresented)
                    .callBarSafeAreaInset(callBarTopPadding)
            }
        } else {
            RosettaColors.Adaptive.background
        }
    }

    /// Call bar height for UIKit `additionalSafeAreaInsets` — pushes nav bar down.
    /// Telegram uses safeInsets.top + 20 (Dynamic Island) / +12 (notch).
    /// 36pt gives a comfortable visible strip below the Dynamic Island.
    private var callBarTopPadding: CGFloat {
        (callManager.uiState.isVisible && callManager.uiState.isMinimized) ? 36 : 0
    }

    private var isAnyChatDetailPresented: Bool {
        isChatListDetailPresented
    }

    private var tabBadges: [TabBadge] {
        guard let chatUnreadBadge else {
            return []
        }
        return [TabBadge(tab: .chats, text: chatUnreadBadge)]
    }

    /// PERF: cached unread count — updated by observation-isolated child view.
    /// Reading DialogRepository.shared.sortedDialogs directly in MainTabView.body
    /// creates observation on the entire dialogs dictionary, causing full body
    /// re-evaluation on every dialog mutation (online, typing, delivery).
    @State private var cachedUnreadCount: Int = 0

    private var chatUnreadBadge: String? {
        if cachedUnreadCount <= 0 {
            return nil
        }
        return cachedUnreadCount > 999 ? "\(cachedUnreadCount / 1000)K" : "\(cachedUnreadCount)"
    }
}

// MARK: - Observation-Isolated Unread Counter

/// Invisible child view that reads DialogRepository in its OWN observation scope.
/// MainTabView.body never observes DialogRepository directly — only this tiny view
/// re-evaluates when dialogs change, updating the parent via @Binding.
private struct UnreadCountObserver: View {
    @Binding var count: Int

    private var unreadTotal: Int {
        DialogRepository.shared.sortedDialogs
            .reduce(0) { $0 + ($1.isMuted ? 0 : $1.unreadCount) }
    }

    var body: some View {
        let total = unreadTotal
        Color.clear
            .frame(width: 0, height: 0)
            .allowsHitTesting(false)
            .task(id: total) { count = total }
    }
}

private extension View {
    /// Non-conditional badge — preserves structural identity.
    /// A @ViewBuilder `if let / else` creates two branches; switching
    /// between them changes the child's structural identity, destroying
    /// any child view identity-dependent state.
    func badgeIfNeeded(_ value: String?) -> some View {
        badge(Text(value ?? ""))
    }
}

// MARK: - Pager Offset Modifier

/// Isolates the offset/animation from child view identity so that
/// changing `effectiveIndex` only redraws the transform, not the child views.
private struct PagerOffsetModifier: ViewModifier {
    let effectiveIndex: CGFloat
    let pageWidth: CGFloat
    let isDragging: Bool

    func body(content: Content) -> some View {
        content
            .offset(x: -effectiveIndex * pageWidth)
            .animation(
                isDragging ? nil : .spring(response: 0.34, dampingFraction: 0.82),
                value: effectiveIndex
            )
    }
}

// MARK: - Placeholder

struct PlaceholderTabView: View {
    let title: String
    let icon: String

    var body: some View {
        NavigationStack {
            ZStack {
                RosettaColors.Adaptive.background
                    .ignoresSafeArea()

                VStack(spacing: 16) {
                    Image(systemName: icon)
                        .font(.system(size: 52))
                        .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.5))

                    Text(title)
                        .font(.system(size: 17, weight: .semibold))
                        .foregroundStyle(RosettaColors.Adaptive.textSecondary)

                    Text("Coming soon")
                        .font(.system(size: 15))
                        .foregroundStyle(RosettaColors.Adaptive.textTertiary)
                }
            }
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Text(title)
                        .font(.system(size: 17, weight: .semibold))
                        .foregroundStyle(RosettaColors.Adaptive.text)
                }
            }
            .applyGlassNavBar()
        }
    }
}

// MARK: - Device Confirm Overlay (observation-isolated)

/// Reads `ProtocolManager` in its own observation scope.
/// Shows full-screen `DeviceConfirmView` when this device awaits approval.
private struct DeviceConfirmOverlay: View {
    var body: some View {
        if ProtocolManager.shared.connectionState == .deviceVerificationRequired {
            DeviceConfirmView()
                .transition(.opacity)
        }
    }
}
