import SwiftUI

// MARK: - GroupInfoView

/// Telegram-parity group info screen.
/// Layout: header → description → action buttons (mute/search/more) → members.
/// All colors are adaptive for light/dark theme support.
struct GroupInfoView: View {
    @StateObject private var viewModel: GroupInfoViewModel
    @Environment(\.dismiss) private var dismiss

    @State private var showLeaveAlert = false
    @State private var showMoreSheet = false
    @State private var memberToKick: GroupMember?
    @State private var isMuted = false
    @State private var selectedMemberRoute: ChatRoute?
    @State private var showMemberChat = false
    @State private var selectedTab: GroupInfoTab = .members
    @State private var showEncryptionKeyPage = false
    @State private var isLargeHeader = false
    @State private var topInset: CGFloat = 0
    @State private var navController: UINavigationController?
    @Namespace private var tabNamespace
    @Environment(\.colorScheme) private var colorScheme

    enum GroupInfoTab: String, CaseIterable {
        case members = "Members"
        case media = "Media"
        case files = "Files"
        case voice = "Voice"
        case links = "Links"
    }

    /// Telegram accent blue (#3e88f7) — same on both themes.
    private let accentBlue = Color(hex: 0x3e88f7)

    init(groupDialogKey: String) {
        _viewModel = StateObject(wrappedValue: GroupInfoViewModel(groupDialogKey: groupDialogKey))
        // Start expanded if group already has avatar photo (no animation jump)
        let hasAvatar = AvatarRepository.shared.loadAvatar(publicKey: groupDialogKey) != nil
        _isLargeHeader = State(initialValue: hasAvatar)
    }

    private var groupAvatar: UIImage? {
        AvatarRepository.shared.loadAvatar(publicKey: viewModel.groupDialogKey)
    }

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

            groupScrollContent
                .scrollIndicators(.hidden)
                .modifier(GroupScrollTracker(isLargeHeader: $isLargeHeader, topInset: $topInset, canExpand: groupAvatar != nil))

            if viewModel.isLeaving {
                Color.black.opacity(0.5)
                    .ignoresSafeArea()
                    .overlay {
                        ProgressView().tint(RosettaColors.Adaptive.text).scaleEffect(1.2)
                    }
            }
        }
        .navigationBarTitleDisplayMode(.inline)
        .navigationBarBackButtonHidden(true)
        .enableSwipeBack()
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Button { dismiss() } label: {
                    TelegramVectorIcon(
                        pathData: TelegramIconPath.backChevron,
                        viewBox: CGSize(width: 11, height: 20),
                        color: RosettaColors.Adaptive.text
                    )
                    .frame(width: 11, height: 20)
                    .frame(width: 44, height: 44)
                    .contentShape(Rectangle())
                    .background { TelegramGlassCapsule() }
                }
                .buttonStyle(.plain)
                .padding(.leading, -8) // align with ChatDetail back button (8pt from edge)
            }
        }
        .toolbarBackground(.hidden, for: .navigationBar)
        .background {
            // Invisible UIKit bridge to access the UINavigationController
            NavigationControllerAccessor { nav in
                self.navController = nav
            }
        }
        .onChange(of: showMemberChat) { show in
            guard show, let route = selectedMemberRoute else { return }
            showMemberChat = false
            let profileVC = OpponentProfileViewController(route: route, showMessageButton: true)
            profileVC.groupContext = (dialogKey: viewModel.groupDialogKey, isAdmin: viewModel.isAdmin)
            navController?.pushViewController(profileVC, animated: true)
        }
        .onChange(of: showEncryptionKeyPage) { show in
            guard show else { return }
            showEncryptionKeyPage = false
            let vc = UIHostingController(rootView: EncryptionKeyView(groupDialogKey: viewModel.groupDialogKey))
            vc.navigationItem.hidesBackButton = true
            navController?.pushViewController(vc, animated: true)
        }
        .task {
            await viewModel.loadMembers()
            viewModel.loadSharedContent()
            let dialog = DialogRepository.shared.dialogs[viewModel.groupDialogKey]
            isMuted = dialog?.isMuted ?? false
        }
        .onChange(of: viewModel.didLeaveGroup) { left in
            if left { dismiss() }
        }
        .modifier(GroupInfoAlertsModifier(
            showMoreSheet: $showMoreSheet,
            showLeaveAlert: $showLeaveAlert,
            memberToKick: $memberToKick,
            hasError: viewModel.errorMessage != nil,
            errorText: viewModel.errorMessage ?? "",
            onDismissError: { viewModel.errorMessage = nil },
            onLeave: { Task { await viewModel.leaveGroup() } },
            onKick: { key in Task { await viewModel.kickMember(publicKey: key) } },
            moreSheetContent: moreSheetButtons
        ))
    }
}

// MARK: - Alerts Modifier (extracted to fix compiler type-check limit)

private struct GroupInfoAlertsModifier<SheetContent: View>: ViewModifier {
    @Binding var showMoreSheet: Bool
    @Binding var showLeaveAlert: Bool
    @Binding var memberToKick: GroupMember?
    let hasError: Bool
    let errorText: String
    let onDismissError: () -> Void
    let onLeave: () -> Void
    let onKick: (String) -> Void
    let moreSheetContent: SheetContent

    func body(content: Content) -> some View {
        content
            .confirmationDialog("", isPresented: $showMoreSheet, titleVisibility: .hidden) {
                moreSheetContent
            }
            .alert("Leave Group", isPresented: $showLeaveAlert) {
                Button("Leave", role: .destructive, action: onLeave)
                Button("Cancel", role: .cancel) {}
            } message: {
                Text("Are you sure you want to leave this group? You will lose access to all messages.")
            }
            .alert("Remove Member", isPresented: .init(
                get: { memberToKick != nil },
                set: { if !$0 { memberToKick = nil } }
            )) {
                Button("Remove", role: .destructive) {
                    if let member = memberToKick { onKick(member.id) }
                    memberToKick = nil
                }
                Button("Cancel", role: .cancel) { memberToKick = nil }
            } message: {
                if let member = memberToKick {
                    Text("Remove \(member.title) from this group?")
                }
            }
            .alert("Error", isPresented: .init(
                get: { hasError },
                set: { if !$0 { onDismissError() } }
            )) {
                Button("OK", role: .cancel) {}
            } message: {
                Text(errorText)
            }
    }
}

// MARK: - Sections

private extension GroupInfoView {

    // MARK: Scroll Content (with expandable header via safeAreaInset)

    var groupScrollContent: some View {
        ScrollView(.vertical) {
            LazyVStack(spacing: 0) {
                if !viewModel.groupDescription.isEmpty {
                    descriptionSection
                        .padding(.top, 16)
                }

                encryptionKeyCard
                    .padding(.top, 16)

                tabBar
                    .padding(.top, 20)

                // Tab content
                switch selectedTab {
                case .members:
                    membersSection
                        .padding(.top, 12)

                    if !viewModel.isAdmin {
                        HStack(spacing: 6) {
                            Text("Group administrator has marked in messages with")
                                .font(.system(size: 13))
                                .foregroundStyle(RosettaColors.Adaptive.textSecondary)
                            VerifiedBadge(verified: 3, size: 20, interactive: false)
                        }
                        .frame(maxWidth: .infinity)
                        .padding(.horizontal, 16)
                        .padding(.top, 8)
                    }

                case .media:
                    if viewModel.mediaItems.isEmpty {
                        emptyStateView(icon: "photo.on.rectangle", title: "No Media Yet", subtitle: "Photos and videos shared in this group will appear here.")
                    } else {
                        mediaGrid
                            .padding(.top, 12)
                    }

                case .files:
                    if viewModel.fileItems.isEmpty {
                        emptyStateView(icon: "doc", title: "No Files Yet", subtitle: "Files shared in this group will appear here.")
                    } else {
                        filesList
                            .padding(.top, 12)
                    }

                case .voice:
                    emptyStateView(icon: "mic", title: "No Voice Messages Yet", subtitle: "Voice messages shared in this group will appear here.")

                case .links:
                    if viewModel.linkItems.isEmpty {
                        emptyStateView(icon: "link", title: "No Links Yet", subtitle: "Links shared in this group will appear here.")
                    } else {
                        linksList
                            .padding(.top, 12)
                    }
                }

                Spacer(minLength: 40)
            }
            .safeAreaInset(edge: .top, spacing: 0) {
                PeerProfileHeaderView(
                    isLargeHeader: $isLargeHeader,
                    topInset: $topInset,
                    displayName: viewModel.groupTitle,
                    subtitleText: "\(viewModel.memberCount) members",
                    effectiveVerified: 0,
                    avatarImage: groupAvatar,
                    avatarInitials: RosettaColors.groupInitial(name: viewModel.groupTitle, publicKey: viewModel.groupDialogKey),
                    avatarColorIndex: RosettaColors.avatarColorIndex(for: viewModel.groupTitle, publicKey: viewModel.groupDialogKey),
                    isMuted: isMuted,
                    showCallButton: false,
                    onCall: {},
                    onMuteToggle: {
                        DialogRepository.shared.toggleMute(opponentKey: viewModel.groupDialogKey)
                        isMuted.toggle()
                    },
                    onSearch: { dismiss() },
                    onMore: { showMoreSheet = true }
                )
            }
        }
    }

    // MARK: Description

    var descriptionSection: some View {
        TelegramSectionCard {
            VStack(alignment: .leading, spacing: 2) {
                Text("Description")
                    .font(.system(size: 13))
                    .foregroundStyle(RosettaColors.Adaptive.textSecondary)

                Text(viewModel.groupDescription)
                    .font(.system(size: 15))
                    .foregroundStyle(RosettaColors.Adaptive.text)
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.horizontal, 16)
            .padding(.vertical, 12)
        }
        .padding(.horizontal, 16)
    }

    // MARK: More Action Sheet

    @ViewBuilder
    var moreSheetButtons: some View {
        Button("Copy Invite Link") {
            if let invite = viewModel.inviteString ?? {
                viewModel.generateInvite()
                return viewModel.inviteString
            }() {
                UIPasteboard.general.string = invite
            }
        }

        Button("Leave Group", role: .destructive) {
            showLeaveAlert = true
        }
    }

    // MARK: Members

    var membersSection: some View {
        TelegramSectionCard {
            VStack(spacing: 0) {
                if viewModel.isLoading {
                    HStack {
                        Spacer()
                        ProgressView().tint(RosettaColors.Adaptive.text)
                        Spacer()
                    }
                    .padding(.vertical, 20)
                } else {
                    ForEach(Array(viewModel.members.enumerated()), id: \.element.id) { index, member in
                        memberRow(member)

                        if index < viewModel.members.count - 1 {
                            Rectangle()
                                .fill(telegramSeparatorColor)
                                .frame(height: 1.0 / UIScreen.main.scale)
                                .padding(.leading, 65)
                        }
                    }
                }
            }
        }
        .padding(.horizontal, 16)
    }

    @ViewBuilder
    func memberRow(_ member: GroupMember) -> some View {
        let myKey = SessionManager.shared.currentPublicKey
        let canKick = viewModel.isAdmin && member.id != myKey && !member.isAdmin

        Button {
            guard member.id != myKey else { return }
            selectedMemberRoute = ChatRoute(
                publicKey: member.id,
                title: member.title,
                username: member.username,
                verified: member.verified
            )
            showMemberChat = true
        } label: {
            GroupMemberRow(member: member)
                .padding(.horizontal, 16)
        }
        .buttonStyle(.plain)
        .contextMenu {
            if member.id != myKey {
                Button {
                    let route = ChatRoute(
                        publicKey: member.id,
                        title: member.title,
                        username: member.username,
                        verified: member.verified
                    )
                    let chatVC = ChatDetailViewController(route: route)
                    navController?.pushViewController(chatVC, animated: true)
                } label: {
                    Label("Send Message", systemImage: "bubble.left.fill")
                }
            }

            if canKick {
                Button(role: .destructive) {
                    memberToKick = member
                } label: {
                    Label("Remove Member", systemImage: "person.badge.minus")
                }
            }
        }
        .simultaneousGesture(
            DragGesture(minimumDistance: 50)
                .onEnded { value in
                    guard canKick else { return }
                    if value.translation.width < -50 && abs(value.translation.height) < 30 {
                        memberToKick = member
                    }
                }
        )
    }

    // MARK: Tab Bar (Telegram parity — horizontal capsule tabs with liquid selection)

    private var tabActiveColor: Color { RosettaColors.Adaptive.text }

    private var tabInactiveColor: Color {
        RosettaColors.adaptive(
            light: Color.black.opacity(0.4),
            dark: Color.white.opacity(0.6)
        )
    }

    private var tabIndicatorFill: Color {
        RosettaColors.adaptive(
            light: Color.black.opacity(0.08),
            dark: Color.white.opacity(0.18)
        )
    }

    var tabBar: some View {
        HStack(spacing: 0) {
            ForEach(GroupInfoTab.allCases, id: \.self) { tab in
                Button {
                    withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) {
                        selectedTab = tab
                    }
                } label: {
                    Text(tab.rawValue)
                        .font(.system(size: 15, weight: .medium))
                        .foregroundStyle(selectedTab == tab ? tabActiveColor : tabInactiveColor)
                        .padding(.horizontal, 12)
                        .padding(.vertical, 8)
                        .background {
                            if selectedTab == tab {
                                Capsule()
                                    .fill(tabIndicatorFill)
                                    .matchedGeometryEffect(id: "tab_indicator", in: tabNamespace)
                            }
                        }
                }
                .buttonStyle(.plain)
            }
        }
        .padding(.horizontal, 3)
        .padding(.vertical, 3)
        .background(
            Capsule()
                .fill(telegramSectionFill)
        )
        .padding(.horizontal, 16)
    }

    // MARK: Encryption Key Card

    var encryptionKeyCard: some View {
        let keyString: String = {
            let account = SessionManager.shared.currentPublicKey
            guard let privateKey = SessionManager.shared.privateKeyHex else { return "" }
            return GroupRepository.shared.groupKey(
                account: account,
                privateKeyHex: privateKey,
                groupDialogKey: viewModel.groupDialogKey
            ) ?? ""
        }()

        return Button {
            showEncryptionKeyPage = true
        } label: {
            TelegramSectionCard {
                HStack(spacing: 12) {
                    EncryptionKeyPixelGrid(keyString: keyString, size: 32)
                        .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))

                    Text("Encryption key")
                        .font(.system(size: 17))
                        .foregroundStyle(RosettaColors.Adaptive.text)

                    Spacer(minLength: 0)

                    Image(systemName: "chevron.right")
                        .font(.system(size: 14, weight: .semibold))
                        .foregroundStyle(RosettaColors.Adaptive.textSecondary)
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
            }
        }
        .buttonStyle(.plain)
        .padding(.horizontal, 16)
    }

    // MARK: Files List

    var filesList: some View {
        TelegramSectionCard {
            VStack(spacing: 0) {
                ForEach(Array(viewModel.fileItems.enumerated()), id: \.element.id) { index, file in
                    Button {
                        openFile(file)
                    } label: {
                        HStack(spacing: 12) {
                            Image(systemName: fileIcon(for: file.fileName))
                                .font(.system(size: 24))
                                .foregroundStyle(accentBlue)
                                .frame(width: 40, height: 40)
                                .background(
                                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                                        .fill(accentBlue.opacity(0.12))
                                )

                            VStack(alignment: .leading, spacing: 2) {
                                Text(file.fileName)
                                    .font(.system(size: 16))
                                    .foregroundStyle(RosettaColors.Adaptive.text)
                                    .lineLimit(1)

                                Text(file.subtitle)
                                    .font(.system(size: 13))
                                    .foregroundStyle(RosettaColors.Adaptive.textSecondary)
                            }

                            Spacer()
                        }
                        .padding(.horizontal, 16)
                        .padding(.vertical, 10)
                    }
                    .buttonStyle(.plain)

                    if index < viewModel.fileItems.count - 1 {
                        Rectangle()
                            .fill(telegramSeparatorColor)
                            .frame(height: 1.0 / UIScreen.main.scale)
                            .padding(.leading, 68)
                    }
                }
            }
        }
        .padding(.horizontal, 16)
    }

    private func fileIcon(for fileName: String) -> String {
        let ext = (fileName as NSString).pathExtension.lowercased()
        switch ext {
        case "pdf": return "doc.richtext.fill"
        case "doc", "docx": return "doc.text.fill"
        case "xls", "xlsx": return "tablecells.fill"
        case "ppt", "pptx": return "slider.horizontal.below.rectangle"
        case "zip", "rar", "7z", "tar", "gz": return "doc.zipper"
        case "mp3", "wav", "aac", "m4a", "ogg": return "music.note"
        case "mp4", "mov", "avi", "mkv": return "film"
        case "jpg", "jpeg", "png", "gif", "webp", "heic": return "photo"
        case "txt", "md", "rtf": return "doc.plaintext"
        case "json", "xml", "csv": return "doc.text"
        case "swift", "js", "ts", "py", "java", "kt": return "chevron.left.forwardslash.chevron.right"
        default: return "doc.fill"
        }
    }

    private func openFile(_ file: SharedFileItem) {
        let parsed = AttachmentPreviewCodec.parseFilePreview(file.preview)
        let tag = parsed.downloadTag
        guard !tag.isEmpty else { return }

        // Try loading from cache first
        if let data = AttachmentCache.shared.loadFileData(forAttachmentId: file.attachmentId, fileName: file.fileName) {
            shareFileData(data, fileName: file.fileName)
            return
        }

        // Download from CDN and decrypt
        Task {
            do {
                let rawData = try await TransportManager.shared.downloadFile(tag: tag)
                let encryptedString = String(decoding: rawData, as: UTF8.self)

                // Get group key and build password candidates
                let account = SessionManager.shared.currentPublicKey
                guard let privateKey = SessionManager.shared.privateKeyHex else { return }
                let groupKey = GroupRepository.shared.groupKey(
                    account: account, privateKeyHex: privateKey, groupDialogKey: viewModel.groupDialogKey
                ) ?? ""

                let candidates = groupKey.isEmpty ? [] : MessageCrypto.attachmentPasswordCandidates(from: groupKey)

                // Try each password candidate
                var decryptedData: Data?
                for password in candidates {
                    // Try with compression first (standard path)
                    if let d = try? CryptoManager.shared.decryptWithPassword(
                        encryptedString, password: password, requireCompression: true
                    ) {
                        decryptedData = d
                        break
                    }
                    // Fallback without compression requirement
                    if let d = try? CryptoManager.shared.decryptWithPassword(
                        encryptedString, password: password
                    ) {
                        decryptedData = d
                        break
                    }
                }

                // Parse data URI if present (CDN stores as data:...;base64,<blob>)
                let fileData: Data
                if let decrypted = decryptedData {
                    if let dataStr = String(data: decrypted, encoding: .utf8),
                       let range = dataStr.range(of: ";base64,"),
                       let binaryData = Data(base64Encoded: String(dataStr[range.upperBound...])) {
                        fileData = binaryData
                    } else {
                        fileData = decrypted
                    }
                } else {
                    // Fallback: use raw downloaded data
                    fileData = rawData
                }

                AttachmentCache.shared.saveFile(fileData, forAttachmentId: file.attachmentId, fileName: file.fileName)
                shareFileData(fileData, fileName: file.fileName)
            } catch {
                // Show error feedback on main thread
            }
        }
    }

    private func shareFileData(_ data: Data, fileName: String) {
        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
        try? data.write(to: tempURL)
        let activityVC = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
        if let popover = activityVC.popoverPresentationController {
            popover.sourceView = UIView()
        }
        guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let rootVC = scene.windows.first?.rootViewController else { return }
        var topVC = rootVC
        while let presented = topVC.presentedViewController {
            topVC = presented
        }
        topVC.present(activityVC, animated: true)
    }

    // MARK: Links List

    var linksList: some View {
        TelegramSectionCard {
            VStack(spacing: 0) {
                ForEach(Array(viewModel.linkItems.enumerated()), id: \.element.id) { index, link in
                    Button {
                        if let url = URL(string: link.url) {
                            UIApplication.shared.open(url)
                        }
                    } label: {
                        HStack(spacing: 12) {
                            Text(String(link.displayHost.prefix(1)).uppercased())
                                .font(.system(size: 18, weight: .bold))
                                .foregroundStyle(.white)
                                .frame(width: 40, height: 40)
                                .background(
                                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                                        .fill(accentBlue)
                                )

                            VStack(alignment: .leading, spacing: 2) {
                                Text(link.displayHost)
                                    .font(.system(size: 16, weight: .medium))
                                    .foregroundStyle(RosettaColors.Adaptive.text)
                                    .lineLimit(1)

                                Text(link.context)
                                    .font(.system(size: 13))
                                    .foregroundStyle(RosettaColors.Adaptive.textSecondary)
                                    .lineLimit(2)
                            }

                            Spacer()
                        }
                        .padding(.horizontal, 16)
                        .padding(.vertical, 10)
                    }
                    .buttonStyle(.plain)

                    if index < viewModel.linkItems.count - 1 {
                        Rectangle()
                            .fill(telegramSeparatorColor)
                            .frame(height: 1.0 / UIScreen.main.scale)
                            .padding(.leading, 68)
                    }
                }
            }
        }
        .padding(.horizontal, 16)
    }

    // MARK: Empty State (Telegram parity)

    func emptyStateView(icon: String, title: String, subtitle: String) -> some View {
        VStack(spacing: 12) {
            Image(systemName: icon)
                .font(.system(size: 48))
                .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.5))

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

            Text(subtitle)
                .font(.system(size: 15))
                .foregroundStyle(RosettaColors.Adaptive.textSecondary)
                .multilineTextAlignment(.center)
                .padding(.horizontal, 32)
        }
        .frame(maxWidth: .infinity)
        .padding(.top, 60)
        .padding(.bottom, 40)
    }

    // MARK: Media Grid (Telegram parity — edge-to-edge, 3 columns, 1px spacing)

    var mediaGrid: some View {
        let columns = Array(repeating: GridItem(.flexible(), spacing: 1), count: 3)
        return LazyVGrid(columns: columns, spacing: 1) {
            ForEach(viewModel.mediaItems) { item in
                SharedMediaTileView(item: item, allItems: viewModel.mediaItems)
            }
        }
    }
}

// MARK: - SharedMediaTileView (edge-to-edge tile for media grid)

private struct SharedMediaTileView: View {
    let item: SharedMediaItem
    let allItems: [SharedMediaItem]

    @State private var image: UIImage?
    @State private var blurImage: UIImage?

    var body: some View {
        GeometryReader { proxy in
            ZStack {
                if let image {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFill()
                        .frame(width: proxy.size.width, height: proxy.size.width)
                        .clipped()
                } else if let blurImage {
                    Image(uiImage: blurImage)
                        .resizable()
                        .scaledToFill()
                        .frame(width: proxy.size.width, height: proxy.size.width)
                        .clipped()
                } else {
                    RosettaColors.Adaptive.surface
                }
            }
            .contentShape(Rectangle())
            .onTapGesture { openGallery() }
        }
        .aspectRatio(1, contentMode: .fit)
        .task { loadImages() }
    }

    private func loadImages() {
        if let cached = AttachmentCache.shared.loadImage(forAttachmentId: item.attachmentId) {
            image = cached
        } else if !item.blurhash.isEmpty {
            blurImage = BlurHashDecoder.decode(blurHash: item.blurhash, width: 32, height: 32)
        }
    }

    private func openGallery() {
        let viewableImages = allItems.map { mediaItem in
            ViewableImageInfo(
                attachmentId: mediaItem.attachmentId,
                messageId: mediaItem.messageId,
                senderName: mediaItem.senderName,
                timestamp: Date(timeIntervalSince1970: Double(mediaItem.timestamp) / 1000.0),
                caption: mediaItem.caption
            )
        }
        let index = allItems.firstIndex(where: { $0.attachmentId == item.attachmentId }) ?? 0
        let state = ImageViewerState(
            images: viewableImages,
            initialIndex: index,
            sourceFrame: .zero
        )
        ImageViewerPresenter.shared.present(state: state)
    }
}

// MARK: - Scroll Tracking (iOS 18+ with fallback)

private struct GroupScrollTracker: ViewModifier {
    @Binding var isLargeHeader: Bool
    @Binding var topInset: CGFloat
    let canExpand: Bool

    func body(content: Content) -> some View {
        if #available(iOS 18, *) {
            GroupIOS18ScrollTracker(isLargeHeader: $isLargeHeader, topInset: $topInset, canExpand: canExpand) {
                content
            }
        } else {
            content
        }
    }
}

@available(iOS 18, *)
private struct GroupIOS18ScrollTracker<Content: View>: View {
    @Binding var isLargeHeader: Bool
    @Binding var topInset: CGFloat
    let canExpand: Bool
    @State private var scrollPhase: ScrollPhase = .idle
    let content: () -> Content

    var body: some View {
        content()
            .onScrollGeometryChange(for: CGFloat.self) {
                $0.contentInsets.top
            } action: { _, newValue in
                topInset = newValue
            }
            .onScrollGeometryChange(for: CGFloat.self) {
                $0.contentOffset.y + $0.contentInsets.top
            } action: { _, newValue in
                if scrollPhase == .interacting {
                    withAnimation(.snappy(duration: 0.2, extraBounce: 0)) {
                        isLargeHeader = canExpand && (newValue < -10 || (isLargeHeader && newValue < 0))
                    }
                }
            }
            .onScrollPhaseChange { _, newPhase in
                scrollPhase = newPhase
            }
    }

}

// MARK: - UIKit Navigation Bridge

/// Invisible UIView that captures the nearest UINavigationController via responder chain.
struct NavigationControllerAccessor: UIViewRepresentable {
    let callback: (UINavigationController?) -> Void

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.isHidden = true
        view.isUserInteractionEnabled = false
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        // Walk the responder chain to find the owning UINavigationController
        DispatchQueue.main.async {
            var responder: UIResponder? = uiView
            while let r = responder {
                if let nav = r as? UINavigationController {
                    callback(nav)
                    return
                }
                responder = r.next
            }
            // Fallback: check parent VC
            if let vc = uiView.parentViewController {
                callback(vc.navigationController)
            }
        }
    }
}

extension UIView {
    var parentViewController: UIViewController? {
        var responder: UIResponder? = self.next
        while let r = responder {
            if let vc = r as? UIViewController { return vc }
            responder = r.next
        }
        return nil
    }
}
