import UIKit
import SwiftUI
import Combine

// MARK: - GroupInfoViewController

/// Telegram-parity group info screen (UIKit). Replaces SwiftUI GroupInfoView.
/// Expandable header, description, encryption key card, 5 tabs (Members/Media/Files/Voice/Links).
final class GroupInfoViewController: UIViewController, UIScrollViewDelegate,
    PeerProfileHeaderDelegate, ProfileTabBarDelegate, UIGestureRecognizerDelegate,
    UIContextMenuInteractionDelegate {

    // MARK: - Properties

    private let groupDialogKey: String
    private let viewModel: GroupInfoViewModel
    private var cancellables = Set<AnyCancellable>()

    // MARK: - Subviews

    private let scrollView = UIScrollView()
    private let contentView = UIView()
    private let headerView = PeerProfileHeaderUIView()
    private let descriptionCard = UIView()
    private let encryptionKeyCard = UIControl()
    private let tabBar: ProfileTabBarView
    private let membersCard = UIView()
    private var mediaCollectionView: UICollectionView!
    private let filesContainer = UIView()
    private let linksContainer = UIView()
    private let emptyStateView = UIView()
    private let emptyStateIcon = UIImageView()
    private let emptyStateTitleLabel = UILabel()
    private let emptyStateSubLabel = UILabel()
    private let adminHintView = UIView()
    private let loadingOverlay = UIView()
    private let loadingSpinner = UIActivityIndicatorView(style: .medium)

    private var selectedTab = 0
    private var addedSwipeBackGesture = false
    private var isMuted = false

    // Colors
    private let sectionFill = UIColor { $0.userInterfaceStyle == .dark
        ? UIColor(red: 28/255, green: 28/255, blue: 29/255, alpha: 1)
        : UIColor(red: 242/255, green: 242/255, blue: 247/255, alpha: 1)
    }
    private let separatorColor = UIColor { $0.userInterfaceStyle == .dark
        ? UIColor(red: 0x54/255, green: 0x54/255, blue: 0x58/255, alpha: 0.55)
        : UIColor(red: 0x3C/255, green: 0x3C/255, blue: 0x43/255, alpha: 0.36)
    }
    private let primaryBlue = UIColor(red: 0x24/255, green: 0x8A/255, blue: 0xE6/255, alpha: 1)
    private let accentBlue = UIColor(red: 0x3E/255, green: 0x88/255, blue: 0xF7/255, alpha: 1)
    private let textColor = UIColor { $0.userInterfaceStyle == .dark ? .white : .black }
    private let textSecondary = UIColor { $0.userInterfaceStyle == .dark
        ? UIColor(red: 0x8D/255, green: 0x8E/255, blue: 0x93/255, alpha: 1)
        : UIColor(red: 0x8E/255, green: 0x8E/255, blue: 0x93/255, alpha: 1)
    }

    // MARK: - Init

    init(groupDialogKey: String) {
        self.groupDialogKey = groupDialogKey
        self.viewModel = GroupInfoViewModel(groupDialogKey: groupDialogKey)
        self.tabBar = ProfileTabBarView(titles: ["Members", "Media", "Files", "Voice", "Links"])
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable) required init?(coder: NSCoder) { fatalError() }

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.setNavigationBarHidden(true, animated: false)
        view.backgroundColor = UIColor { $0.userInterfaceStyle == .dark ? .black : .white }

        setupScrollView()
        setupHeader()
        setupDescriptionCard()
        setupEncryptionKeyCard()
        setupTabBar()
        setupMembersCard()
        setupMediaGrid()
        setupListContainers()
        setupEmptyState()
        setupAdminHint()
        setupLoadingOverlay()
        bindViewModel()

        let hasAvatar = AvatarRepository.shared.loadAvatar(publicKey: groupDialogKey) != nil
        headerView.setLargeHeader(hasAvatar, animated: false)
        updateHeaderContent()

        let dialog = DialogRepository.shared.dialogs[groupDialogKey]
        isMuted = dialog?.isMuted ?? false

        Task { await viewModel.loadMembers() }
        viewModel.loadSharedContent()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setupFullWidthSwipeBack()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if isMovingFromParent {
            navigationController?.setNavigationBarHidden(true, animated: true)
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        layoutContent()
    }

    // MARK: - Setup

    private func setupScrollView() {
        scrollView.delegate = self
        scrollView.showsVerticalScrollIndicator = false
        scrollView.contentInsetAdjustmentBehavior = .never
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
    }

    private func setupHeader() {
        headerView.delegate = self
        headerView.onHeightChanged = { [weak self] in
            self?.view.setNeedsLayout()
        }
        contentView.addSubview(headerView)
    }

    private func setupDescriptionCard() {
        descriptionCard.backgroundColor = sectionFill
        descriptionCard.layer.cornerRadius = 11
        descriptionCard.layer.cornerCurve = .continuous
        descriptionCard.isHidden = true
        contentView.addSubview(descriptionCard)
    }

    private func setupEncryptionKeyCard() {
        encryptionKeyCard.backgroundColor = sectionFill
        encryptionKeyCard.layer.cornerRadius = 11
        encryptionKeyCard.layer.cornerCurve = .continuous
        encryptionKeyCard.addTarget(self, action: #selector(encryptionKeyTapped), for: .touchUpInside)
        contentView.addSubview(encryptionKeyCard)
    }

    private func setupTabBar() {
        tabBar.delegate = self
        contentView.addSubview(tabBar)
    }

    private func setupMembersCard() {
        membersCard.backgroundColor = sectionFill
        membersCard.layer.cornerRadius = 11
        membersCard.layer.cornerCurve = .continuous
        membersCard.isHidden = true
        contentView.addSubview(membersCard)
    }

    private func setupMediaGrid() {
        let itemSize = (UIScreen.main.bounds.width - 2) / 3
        let layout = UICollectionViewCompositionalLayout { _, _ in
            let item = NSCollectionLayoutItem(layoutSize: .init(
                widthDimension: .fractionalWidth(1.0 / 3.0),
                heightDimension: .absolute(itemSize)
            ))
            let group = NSCollectionLayoutGroup.horizontal(
                layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(itemSize)),
                repeatingSubitem: item,
                count: 3
            )
            group.interItemSpacing = .fixed(1)
            let section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = 1
            return section
        }

        mediaCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        mediaCollectionView.register(GroupMediaTileCell.self, forCellWithReuseIdentifier: "media")
        mediaCollectionView.dataSource = self
        mediaCollectionView.delegate = self
        mediaCollectionView.backgroundColor = .clear
        mediaCollectionView.isScrollEnabled = false
        mediaCollectionView.isHidden = true
        contentView.addSubview(mediaCollectionView)
    }

    private func setupListContainers() {
        for container in [filesContainer, linksContainer] {
            container.isHidden = true
            contentView.addSubview(container)
        }
    }

    private func setupEmptyState() {
        emptyStateIcon.contentMode = .scaleAspectFit
        emptyStateIcon.tintColor = textSecondary.withAlphaComponent(0.5)
        emptyStateView.addSubview(emptyStateIcon)

        emptyStateTitleLabel.font = .systemFont(ofSize: 17, weight: .semibold)
        emptyStateTitleLabel.textColor = textColor
        emptyStateTitleLabel.textAlignment = .center
        emptyStateView.addSubview(emptyStateTitleLabel)

        emptyStateSubLabel.font = .systemFont(ofSize: 15)
        emptyStateSubLabel.textColor = textSecondary
        emptyStateSubLabel.textAlignment = .center
        emptyStateSubLabel.numberOfLines = 0
        emptyStateView.addSubview(emptyStateSubLabel)

        emptyStateView.isHidden = true
        contentView.addSubview(emptyStateView)
    }

    private func setupAdminHint() {
        adminHintView.isHidden = true
        contentView.addSubview(adminHintView)
    }

    private func setupLoadingOverlay() {
        loadingOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        loadingOverlay.isHidden = true
        loadingSpinner.color = .white
        loadingSpinner.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
        loadingOverlay.addSubview(loadingSpinner)
        view.addSubview(loadingOverlay)
    }

    // MARK: - Layout

    private func layoutContent() {
        let w = view.bounds.width
        let safeTop = view.safeAreaInsets.top

        scrollView.frame = view.bounds
        contentView.frame = CGRect(x: 0, y: 0, width: w, height: 10000)

        // Header
        headerView.frame = CGRect(x: 0, y: safeTop, width: w, height: headerView.preferredHeight())
        headerView.layoutSubviews()

        var y = headerView.frame.maxY + 16

        // Description card
        if !viewModel.groupDescription.isEmpty {
            descriptionCard.isHidden = false
            let descH = layoutDescriptionCard(width: w - 32)
            descriptionCard.frame = CGRect(x: 16, y: y, width: w - 32, height: descH)
            y = descriptionCard.frame.maxY + 16
        } else {
            descriptionCard.isHidden = true
        }

        // Encryption key card
        let keyH = layoutEncryptionKeyCard(width: w - 32)
        encryptionKeyCard.frame = CGRect(x: 16, y: y, width: w - 32, height: keyH)
        y = encryptionKeyCard.frame.maxY + 20

        // Tab bar
        tabBar.frame = CGRect(x: 16, y: y, width: w - 32, height: 38)
        y = tabBar.frame.maxY + 12

        // Tab content
        layoutTabContent(at: y, width: w)

        // Update scroll content size
        let bottomY = calculateBottomY(from: y) + 40
        contentView.frame.size.height = bottomY
        scrollView.contentSize = CGSize(width: w, height: bottomY)

        // Loading overlay
        loadingOverlay.frame = view.bounds
        loadingSpinner.center = CGPoint(x: w / 2, y: view.bounds.height / 2)
    }

    private func calculateBottomY(from contentY: CGFloat) -> CGFloat {
        if !emptyStateView.isHidden { return emptyStateView.frame.maxY }
        if !membersCard.isHidden {
            let base = membersCard.frame.maxY
            return adminHintView.isHidden ? base : adminHintView.frame.maxY
        }
        if !mediaCollectionView.isHidden { return mediaCollectionView.frame.maxY }
        if !filesContainer.isHidden { return filesContainer.frame.maxY }
        if !linksContainer.isHidden { return linksContainer.frame.maxY }
        return contentY + 200
    }

    // MARK: - Description Layout

    private func layoutDescriptionCard(width: CGFloat) -> CGFloat {
        descriptionCard.subviews.forEach { $0.removeFromSuperview() }

        let label = UILabel()
        label.text = "Description"
        label.font = .systemFont(ofSize: 13)
        label.textColor = textSecondary
        label.frame = CGRect(x: 16, y: 12, width: width - 32, height: 16)
        descriptionCard.addSubview(label)

        let desc = UILabel()
        desc.text = viewModel.groupDescription
        desc.font = .systemFont(ofSize: 15)
        desc.textColor = textColor
        desc.numberOfLines = 0
        let descSize = desc.sizeThatFits(CGSize(width: width - 32, height: .greatestFiniteMagnitude))
        desc.frame = CGRect(x: 16, y: 30, width: width - 32, height: descSize.height)
        descriptionCard.addSubview(desc)

        return 30 + descSize.height + 12
    }

    // MARK: - Encryption Key Layout

    private var encryptionKeyHosting: UIHostingController<EncryptionKeyPixelGrid>?

    private func layoutEncryptionKeyCard(width: CGFloat) -> CGFloat {
        encryptionKeyCard.subviews.forEach { $0.removeFromSuperview() }
        encryptionKeyHosting?.removeFromParent()

        let keyString: String = {
            let account = SessionManager.shared.currentPublicKey
            guard let pk = SessionManager.shared.privateKeyHex else { return "" }
            return GroupRepository.shared.groupKey(account: account, privateKeyHex: pk, groupDialogKey: groupDialogKey) ?? ""
        }()

        let pixelGrid = EncryptionKeyPixelGrid(keyString: keyString, size: 32)
        let hosting = UIHostingController(rootView: pixelGrid)
        hosting.view.backgroundColor = .clear
        hosting.view.frame = CGRect(x: 16, y: 12, width: 32, height: 32)
        hosting.view.clipsToBounds = true
        hosting.view.layer.cornerRadius = 6
        encryptionKeyCard.addSubview(hosting.view)
        encryptionKeyHosting = hosting

        let label = UILabel()
        label.text = "Encryption key"
        label.font = .systemFont(ofSize: 17)
        label.textColor = textColor
        label.frame = CGRect(x: 60, y: 16, width: width - 100, height: 22)
        encryptionKeyCard.addSubview(label)

        let chevron = UIImageView(image: UIImage(systemName: "chevron.right")?.withConfiguration(
            UIImage.SymbolConfiguration(pointSize: 14, weight: .semibold)))
        chevron.tintColor = textSecondary
        chevron.frame = CGRect(x: width - 30, y: 19, width: 14, height: 18)
        encryptionKeyCard.addSubview(chevron)

        return 56
    }

    // MARK: - Tab Content

    private func layoutTabContent(at y: CGFloat, width: CGFloat) {
        membersCard.isHidden = true
        mediaCollectionView.isHidden = true
        filesContainer.isHidden = true
        linksContainer.isHidden = true
        emptyStateView.isHidden = true
        adminHintView.isHidden = true

        switch selectedTab {
        case 0: // Members
            membersCard.isHidden = false
            let h = layoutMembersList(width: width - 32)
            membersCard.frame = CGRect(x: 16, y: y, width: width - 32, height: h)

            if !viewModel.isAdmin {
                layoutAdminHint(at: membersCard.frame.maxY + 8, width: width)
            }

        case 1: // Media
            if viewModel.mediaItems.isEmpty {
                showEmpty(icon: "photo.on.rectangle", title: "No Media Yet",
                          subtitle: "Photos and videos shared in this group will appear here.",
                          at: y, width: width)
            } else {
                mediaCollectionView.isHidden = false
                let rows = ceil(Double(viewModel.mediaItems.count) / 3.0)
                let itemH = (width - 2) / 3
                let gridH = CGFloat(rows) * itemH + CGFloat(max(0, rows - 1))
                mediaCollectionView.frame = CGRect(x: 0, y: y, width: width, height: gridH)
                mediaCollectionView.reloadData()
            }

        case 2: // Files
            if viewModel.fileItems.isEmpty {
                showEmpty(icon: "doc", title: "No Files Yet",
                          subtitle: "Files shared in this group will appear here.",
                          at: y, width: width)
            } else {
                filesContainer.isHidden = false
                let h = layoutFilesList(width: width - 32)
                filesContainer.frame = CGRect(x: 16, y: y, width: width - 32, height: h)
            }

        case 3: // Voice
            showEmpty(icon: "mic", title: "No Voice Messages Yet",
                      subtitle: "Voice messages shared in this group will appear here.",
                      at: y, width: width)

        case 4: // Links
            if viewModel.linkItems.isEmpty {
                showEmpty(icon: "link", title: "No Links Yet",
                          subtitle: "Links shared in this group will appear here.",
                          at: y, width: width)
            } else {
                linksContainer.isHidden = false
                let h = layoutLinksList(width: width - 32)
                linksContainer.frame = CGRect(x: 16, y: y, width: width - 32, height: h)
            }

        default: break
        }
    }

    private func showEmpty(icon: String, title: String, subtitle: String, at y: CGFloat, width: CGFloat) {
        emptyStateView.isHidden = false
        emptyStateIcon.image = UIImage(systemName: icon)?.withConfiguration(
            UIImage.SymbolConfiguration(pointSize: 48, weight: .regular))
        emptyStateTitleLabel.text = title
        emptyStateSubLabel.text = subtitle

        let subSize = emptyStateSubLabel.sizeThatFits(CGSize(width: width - 96, height: 100))
        let totalH: CGFloat = 60 + 60 + 24 + subSize.height + 40

        emptyStateView.frame = CGRect(x: 0, y: y, width: width, height: totalH)
        emptyStateIcon.frame = CGRect(x: (width - 60) / 2, y: 60, width: 60, height: 60)
        emptyStateTitleLabel.frame = CGRect(x: 32, y: 132, width: width - 64, height: 24)
        emptyStateSubLabel.frame = CGRect(x: 48, y: 162, width: width - 96, height: subSize.height)
    }

    // MARK: - Members Layout

    private func layoutMembersList(width: CGFloat) -> CGFloat {
        membersCard.subviews.forEach { $0.removeFromSuperview() }

        if viewModel.isLoading && viewModel.members.isEmpty {
            let spinner = UIActivityIndicatorView(style: .medium)
            spinner.color = textColor
            spinner.startAnimating()
            spinner.center = CGPoint(x: width / 2, y: 30)
            membersCard.addSubview(spinner)
            return 60
        }

        var currentY: CGFloat = 0
        let rowH: CGFloat = 60
        let myKey = SessionManager.shared.currentPublicKey

        for (index, member) in viewModel.members.enumerated() {
            let cell = GroupMemberCell()
            cell.configure(member: member)

            let control = UIControl()
            control.frame = CGRect(x: 0, y: currentY, width: width, height: rowH)
            control.tag = index
            control.addTarget(self, action: #selector(memberTapped(_:)), for: .touchUpInside)
            control.addSubview(cell)
            cell.frame = CGRect(x: 16, y: 0, width: width - 32, height: rowH)

            // Context menu (long-press): "Send Message" + "Remove Member"
            if member.id != myKey {
                let interaction = UIContextMenuInteraction(delegate: self)
                control.addInteraction(interaction)
            }

            // Swipe-to-delete (left swipe → kick alert)
            let canKick = viewModel.isAdmin && member.id != myKey && !member.isAdmin
            if canKick {
                let swipe = UISwipeGestureRecognizer(target: self, action: #selector(memberSwiped(_:)))
                swipe.direction = .left
                control.addGestureRecognizer(swipe)
            }

            membersCard.addSubview(control)
            currentY += rowH

            if index < viewModel.members.count - 1 {
                let sep = UIView()
                sep.backgroundColor = separatorColor
                sep.frame = CGRect(x: 65, y: currentY, width: width - 65, height: 1 / UIScreen.main.scale)
                membersCard.addSubview(sep)
            }
        }

        return currentY
    }

    private func layoutAdminHint(at y: CGFloat, width: CGFloat) {
        adminHintView.isHidden = false
        adminHintView.subviews.forEach { $0.removeFromSuperview() }

        let label = UILabel()
        label.text = "Group administrator has marked in messages with"
        label.font = .systemFont(ofSize: 13)
        label.textColor = textSecondary
        label.numberOfLines = 0
        let labelSize = label.sizeThatFits(CGSize(width: width - 64, height: 60))
        label.frame = CGRect(x: 16, y: 0, width: labelSize.width, height: labelSize.height)
        adminHintView.addSubview(label)

        let badge = VerifiedBadge(verified: 3, size: 20, interactive: false)
        let hosting = UIHostingController(rootView: badge)
        hosting.view.backgroundColor = .clear
        hosting.view.frame = CGRect(x: label.frame.maxX + 6, y: (labelSize.height - 20) / 2, width: 20, height: 20)
        adminHintView.addSubview(hosting.view)

        adminHintView.frame = CGRect(x: 16, y: y, width: width - 32, height: labelSize.height)
    }

    // MARK: - Files Layout

    private func layoutFilesList(width: CGFloat) -> CGFloat {
        filesContainer.subviews.forEach { $0.removeFromSuperview() }
        filesContainer.backgroundColor = sectionFill
        filesContainer.layer.cornerRadius = 11
        filesContainer.layer.cornerCurve = .continuous

        var currentY: CGFloat = 0

        for (index, file) in viewModel.fileItems.enumerated() {
            let rowH: CGFloat = 60
            let row = UIControl()
            row.frame = CGRect(x: 0, y: currentY, width: width, height: rowH)
            row.tag = index
            row.addTarget(self, action: #selector(fileTapped(_:)), for: .touchUpInside)

            let iconBg = UIView()
            iconBg.backgroundColor = accentBlue.withAlphaComponent(0.12)
            iconBg.layer.cornerRadius = 10
            iconBg.frame = CGRect(x: 16, y: 10, width: 40, height: 40)
            row.addSubview(iconBg)

            let icon = UIImageView(image: UIImage(systemName: fileIcon(for: file.fileName)))
            icon.tintColor = accentBlue
            icon.contentMode = .scaleAspectFit
            icon.frame = CGRect(x: 8, y: 8, width: 24, height: 24)
            iconBg.addSubview(icon)

            let nameLabel = UILabel()
            nameLabel.text = file.fileName
            nameLabel.font = .systemFont(ofSize: 16)
            nameLabel.textColor = textColor
            nameLabel.lineBreakMode = .byTruncatingTail
            nameLabel.frame = CGRect(x: 68, y: 12, width: width - 84, height: 20)
            row.addSubview(nameLabel)

            let subLabel = UILabel()
            subLabel.text = file.subtitle
            subLabel.font = .systemFont(ofSize: 13)
            subLabel.textColor = textSecondary
            subLabel.frame = CGRect(x: 68, y: 34, width: width - 84, height: 18)
            row.addSubview(subLabel)

            filesContainer.addSubview(row)
            currentY += rowH

            if index < viewModel.fileItems.count - 1 {
                let sep = UIView()
                sep.backgroundColor = separatorColor
                sep.frame = CGRect(x: 68, y: currentY, width: width - 68, height: 1 / UIScreen.main.scale)
                filesContainer.addSubview(sep)
            }
        }

        return currentY
    }

    // MARK: - Links Layout

    private func layoutLinksList(width: CGFloat) -> CGFloat {
        linksContainer.subviews.forEach { $0.removeFromSuperview() }
        linksContainer.backgroundColor = sectionFill
        linksContainer.layer.cornerRadius = 11
        linksContainer.layer.cornerCurve = .continuous

        var currentY: CGFloat = 0

        for (index, link) in viewModel.linkItems.enumerated() {
            let rowH: CGFloat = 60
            let row = UIControl()
            row.frame = CGRect(x: 0, y: currentY, width: width, height: rowH)
            row.addAction(UIAction { _ in
                if let url = URL(string: link.url) { UIApplication.shared.open(url) }
            }, for: .touchUpInside)

            let badge = UILabel()
            badge.text = String(link.displayHost.prefix(1)).uppercased()
            badge.font = .systemFont(ofSize: 18, weight: .bold)
            badge.textColor = .white
            badge.textAlignment = .center
            badge.backgroundColor = accentBlue
            badge.layer.cornerRadius = 10
            badge.clipsToBounds = true
            badge.frame = CGRect(x: 16, y: 10, width: 40, height: 40)
            row.addSubview(badge)

            let hostLabel = UILabel()
            hostLabel.text = link.displayHost
            hostLabel.font = .systemFont(ofSize: 16, weight: .medium)
            hostLabel.textColor = textColor
            hostLabel.frame = CGRect(x: 68, y: 12, width: width - 84, height: 20)
            row.addSubview(hostLabel)

            let ctxLabel = UILabel()
            ctxLabel.text = link.context
            ctxLabel.font = .systemFont(ofSize: 13)
            ctxLabel.textColor = textSecondary
            ctxLabel.numberOfLines = 2
            ctxLabel.frame = CGRect(x: 68, y: 34, width: width - 84, height: 18)
            row.addSubview(ctxLabel)

            linksContainer.addSubview(row)
            currentY += rowH

            if index < viewModel.linkItems.count - 1 {
                let sep = UIView()
                sep.backgroundColor = separatorColor
                sep.frame = CGRect(x: 68, y: currentY, width: width - 68, height: 1 / UIScreen.main.scale)
                linksContainer.addSubview(sep)
            }
        }

        return currentY
    }

    // MARK: - File Icon Helper

    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"
        }
    }

    // MARK: - File Download & Share (ported from GroupInfoView)

    @objc private func fileTapped(_ sender: UIControl) {
        guard sender.tag < viewModel.fileItems.count else { return }
        let file = viewModel.fileItems[sender.tag]
        openFile(file)
    }

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

        if let data = AttachmentCache.shared.loadFileData(forAttachmentId: file.attachmentId, fileName: file.fileName) {
            shareFileData(data, fileName: file.fileName)
            return
        }

        Task {
            do {
                let rawData = try await TransportManager.shared.downloadFile(tag: tag)
                let encryptedString = String(decoding: rawData, as: UTF8.self)

                let account = SessionManager.shared.currentPublicKey
                guard let privateKey = SessionManager.shared.privateKeyHex else { return }
                let groupKey = GroupRepository.shared.groupKey(
                    account: account, privateKeyHex: privateKey, groupDialogKey: groupDialogKey
                ) ?? ""

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

                var decryptedData: Data?
                for password in candidates {
                    if let d = try? CryptoManager.shared.decryptWithPassword(
                        encryptedString, password: password, requireCompression: true) {
                        decryptedData = d
                        break
                    }
                    if let d = try? CryptoManager.shared.decryptWithPassword(
                        encryptedString, password: password) {
                        decryptedData = d
                        break
                    }
                }

                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 {
                    fileData = rawData
                }

                AttachmentCache.shared.saveFile(fileData, forAttachmentId: file.attachmentId, fileName: file.fileName)
                shareFileData(fileData, fileName: file.fileName)
            } catch {}
        }
    }

    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 = view
        }
        present(activityVC, animated: true)
    }

    // MARK: - ViewModel Binding

    private func bindViewModel() {
        viewModel.$members
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.view.setNeedsLayout() }
            .store(in: &cancellables)

        viewModel.$mediaItems
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.view.setNeedsLayout() }
            .store(in: &cancellables)

        viewModel.$fileItems
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.view.setNeedsLayout() }
            .store(in: &cancellables)

        viewModel.$linkItems
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.view.setNeedsLayout() }
            .store(in: &cancellables)

        viewModel.$isLeaving
            .receive(on: DispatchQueue.main)
            .sink { [weak self] leaving in
                self?.loadingOverlay.isHidden = !leaving
                if leaving { self?.loadingSpinner.startAnimating() }
                else { self?.loadingSpinner.stopAnimating() }
            }
            .store(in: &cancellables)

        viewModel.$didLeaveGroup
            .receive(on: DispatchQueue.main)
            .sink { [weak self] left in
                if left { self?.navigationController?.popViewController(animated: true) }
            }
            .store(in: &cancellables)

        viewModel.$errorMessage
            .receive(on: DispatchQueue.main)
            .sink { [weak self] msg in
                guard let msg, let self else { return }
                let alert = UIAlertController(title: "Error", message: msg, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in
                    self.viewModel.errorMessage = nil
                })
                self.present(alert, animated: true)
            }
            .store(in: &cancellables)

        viewModel.$memberCount
            .receive(on: DispatchQueue.main)
            .sink { [weak self] count in
                self?.headerView.updateSubtitle("\(count) members")
            }
            .store(in: &cancellables)

        // Optimistic local removal when member is kicked from OpponentProfile
        NotificationCenter.default.publisher(for: .groupMemberKicked)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] notif in
                guard let self,
                      let info = notif.userInfo,
                      let groupKey = info["groupDialogKey"] as? String,
                      groupKey == self.groupDialogKey,
                      let memberKey = info["memberKey"] as? String else { return }
                self.viewModel.members.removeAll { $0.id == memberKey }
                self.viewModel.memberCount = self.viewModel.members.count
            }
            .store(in: &cancellables)
    }

    // MARK: - Header Content

    private func updateHeaderContent() {
        let avatar = AvatarRepository.shared.loadAvatar(publicKey: groupDialogKey)

        headerView.configure(
            displayName: viewModel.groupTitle,
            subtitle: "\(viewModel.memberCount) members",
            verified: 0,
            avatar: avatar,
            initials: RosettaColors.groupInitial(name: viewModel.groupTitle, publicKey: groupDialogKey),
            colorIndex: RosettaColors.avatarColorIndex(for: viewModel.groupTitle, publicKey: groupDialogKey),
            isMuted: isMuted,
            isSavedMessages: false,
            showCall: false,
            showMute: true,
            showMessage: false
        )
    }

    // MARK: - UIScrollViewDelegate

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView === self.scrollView, scrollView.isDragging else { return }
        let offsetY = scrollView.contentOffset.y
        let canExpand = headerView.canExpand

        if canExpand && (offsetY < -10 || (headerView.isLargeHeader && offsetY < 0)) {
            headerView.setLargeHeader(true, animated: true)
        } else if offsetY > 10 {
            headerView.setLargeHeader(false, animated: true)
        }
    }

    // MARK: - PeerProfileHeaderDelegate

    func headerDidTapBack() {
        navigationController?.popViewController(animated: true)
    }

    func headerDidTapCall() {}

    func headerDidTapMuteToggle() {
        DialogRepository.shared.toggleMute(opponentKey: groupDialogKey)
        isMuted = DialogRepository.shared.dialogs[groupDialogKey]?.isMuted ?? false
        headerView.updateMuted(isMuted)
    }

    func headerDidTapSearch() {
        navigationController?.popViewController(animated: true)
    }

    func headerDidTapMore() {
        let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        sheet.addAction(UIAlertAction(title: "Copy Invite Link", style: .default) { [weak self] _ in
            guard let self else { return }
            if let invite = self.viewModel.inviteString ?? {
                self.viewModel.generateInvite()
                return self.viewModel.inviteString
            }() {
                UIPasteboard.general.string = invite
            }
        })
        sheet.addAction(UIAlertAction(title: "Leave Group", style: .destructive) { [weak self] _ in
            self?.showLeaveAlert()
        })
        sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(sheet, animated: true)
    }

    func headerDidTapMessage() {}

    // MARK: - Alerts

    private func showLeaveAlert() {
        let alert = UIAlertController(
            title: "Leave Group",
            message: "Are you sure you want to leave this group? You will lose access to all messages.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Leave", style: .destructive) { [weak self] _ in
            Task { await self?.viewModel.leaveGroup() }
        })
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }

    private func showKickAlert(member: GroupMember) {
        let alert = UIAlertController(
            title: "Remove Member",
            message: "Remove \(member.title) from this group?",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Remove", style: .destructive) { [weak self] _ in
            Task { await self?.viewModel.kickMember(publicKey: member.id) }
        })
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }

    // MARK: - Actions

    @objc private func encryptionKeyTapped() {
        let keyView = EncryptionKeyView(groupDialogKey: groupDialogKey)
        let vc = UIHostingController(rootView: keyView)
        vc.navigationItem.hidesBackButton = true
        navigationController?.pushViewController(vc, animated: true)
    }

    @objc private func memberTapped(_ sender: UIControl) {
        let index = sender.tag
        guard index < viewModel.members.count else { return }
        let member = viewModel.members[index]
        let myKey = SessionManager.shared.currentPublicKey
        guard member.id != myKey else { return }

        let route = ChatRoute(
            publicKey: member.id,
            title: member.title,
            username: member.username,
            verified: member.verified
        )
        let profileVC = OpponentProfileViewController(route: route, showMessageButton: true)
        profileVC.groupContext = (dialogKey: groupDialogKey, isAdmin: viewModel.isAdmin)
        navigationController?.pushViewController(profileVC, animated: true)
    }

    @objc private func memberSwiped(_ gesture: UISwipeGestureRecognizer) {
        guard let control = gesture.view as? UIControl,
              control.tag < viewModel.members.count else { return }
        showKickAlert(member: viewModel.members[control.tag])
    }

    // MARK: - ProfileTabBarDelegate

    func tabBar(_ tabBar: ProfileTabBarView, didSelectTabAt index: Int) {
        selectedTab = index
        view.setNeedsLayout()
    }

    // MARK: - Full-Width Swipe Back

    private func setupFullWidthSwipeBack() {
        guard !addedSwipeBackGesture else { return }
        addedSwipeBackGesture = true

        guard let nav = navigationController,
              let edgeGesture = nav.interactivePopGestureRecognizer,
              let targets = edgeGesture.value(forKey: "targets") as? NSArray,
              targets.count > 0 else { return }

        edgeGesture.isEnabled = true

        let fullWidthGesture = UIPanGestureRecognizer()
        fullWidthGesture.setValue(targets, forKey: "targets")
        fullWidthGesture.delegate = self
        nav.view.addGestureRecognizer(fullWidthGesture)
    }

    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
        let velocity = pan.velocity(in: pan.view)
        return velocity.x > 0 && abs(velocity.x) > abs(velocity.y)
    }
}

// MARK: - UICollectionView DataSource + Delegate

extension GroupInfoViewController: UICollectionViewDataSource, UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        viewModel.mediaItems.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "media", for: indexPath) as! GroupMediaTileCell
        let item = viewModel.mediaItems[indexPath.item]
        cell.configure(with: item)
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let viewable = viewModel.mediaItems.map { item in
            ViewableImageInfo(
                attachmentId: item.attachmentId,
                messageId: item.messageId,
                senderName: item.senderName,
                timestamp: Date(timeIntervalSince1970: Double(item.timestamp) / 1000.0),
                caption: item.caption
            )
        }
        let state = ImageViewerState(images: viewable, initialIndex: indexPath.item, sourceFrame: .zero)
        ImageViewerPresenter.shared.present(state: state)
    }
}

// MARK: - UIContextMenuInteractionDelegate

extension GroupInfoViewController {

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        guard let control = interaction.view as? UIControl else { return nil }
        let index = control.tag
        guard index < viewModel.members.count else { return nil }
        let member = viewModel.members[index]
        let myKey = SessionManager.shared.currentPublicKey

        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
            guard let self else { return UIMenu(children: []) }

            // "Send Message" — opens direct chat
            let sendMessage = UIAction(
                title: "Send Message",
                image: UIImage(systemName: "bubble.left.fill")
            ) { _ in
                let route = ChatRoute(
                    publicKey: member.id,
                    title: member.title,
                    username: member.username,
                    verified: member.verified
                )
                let chatVC = ChatDetailViewController(route: route)
                self.navigationController?.pushViewController(chatVC, animated: true)
            }

            var actions: [UIMenuElement] = [sendMessage]

            // "Remove Member" — admin only, not self, not other admins
            let canKick = self.viewModel.isAdmin && member.id != myKey && !member.isAdmin
            if canKick {
                let remove = UIAction(
                    title: "Remove Member",
                    image: UIImage(systemName: "person.badge.minus"),
                    attributes: .destructive
                ) { _ in
                    self.showKickAlert(member: member)
                }
                actions.append(remove)
            }

            return UIMenu(children: actions)
        }
    }
}

// MARK: - Media Tile Cell

private final class GroupMediaTileCell: UICollectionViewCell {
    private let imageView = UIImageView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        contentView.addSubview(imageView)
        contentView.backgroundColor = UIColor { $0.userInterfaceStyle == .dark
            ? UIColor(red: 0x24/255, green: 0x24/255, blue: 0x24/255, alpha: 1)
            : UIColor(red: 0xF5/255, green: 0xF5/255, blue: 0xF5/255, alpha: 1)
        }
    }

    @available(*, unavailable) required init?(coder: NSCoder) { fatalError() }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = contentView.bounds
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.image = nil
    }

    func configure(with item: SharedMediaItem) {
        if let cached = AttachmentCache.shared.loadImage(forAttachmentId: item.attachmentId) {
            imageView.image = cached
        } else if !item.blurhash.isEmpty,
                  let blur = BlurHashDecoder.decode(blurHash: item.blurhash, width: 32, height: 32) {
            imageView.image = blur
        }
    }
}
