import UIKit
import SwiftUI
import Combine

/// Pure UIKit peer profile screen. Phase 3: expand/collapse header + haptic.
final class OpponentProfileViewController: UIViewController, UIGestureRecognizerDelegate,
    ProfileTabBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate {

    // MARK: - Properties

    private let route: ChatRoute
    var showMessageButton = false
    /// Set by GroupInfoViewController when opening member profile from group.
    var groupContext: (dialogKey: String, isAdmin: Bool)?
    private let viewModel: PeerProfileViewModel
    private var cancellables = Set<AnyCancellable>()
    private var addedSwipeBackGesture = false
    private var selectedTab = 0
    private var copiedField: String?
    private var isMuted = false

    // MARK: - Subviews

    private let scrollView = UIScrollView()
    private let contentView = UIView()
    private let backButton = ChatDetailBackButton()

    // Header
    private let avatarContainer = UIView()
    private var avatarHosting: UIHostingController<AvatarView>?
    private let nameLabel = UILabel()
    private let subtitleLabel = UILabel()

    // Action buttons
    private var actionButtonViews: [(container: UIControl, icon: UIImageView, label: UILabel)] = []

    // Info card
    private let infoCard = UIView()

    // Tab bar
    private let tabBar = ProfileTabBarView(titles: ["Media", "Files", "Links", "Groups"])

    // Media grid
    private var mediaCollectionView: UICollectionView!

    // List containers
    private let filesContainer = UIView()
    private let linksContainer = UIView()
    private let groupsContainer = UIView()

    // Empty state
    private let emptyIcon = UIImageView()
    private let emptyLabel = UILabel()


    // MARK: - Constants

    private let hPad: CGFloat = 16
    private let avatarSize: CGFloat = 100
    private let buttonSpacing: CGFloat = 6
    private let buttonCornerRadius: CGFloat = 15
    private let cardCornerRadius: CGFloat = 11

    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 textPrimary = 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)
    }
    private let onlineColor = UIColor(red: 0x34/255, green: 0xC7/255, blue: 0x59/255, alpha: 1)

    // MARK: - Init

    init(route: ChatRoute, showMessageButton: Bool = false) {
        self.route = route
        self.showMessageButton = showMessageButton
        self.viewModel = PeerProfileViewModel(dialogKey: route.publicKey)
        super.init(nibName: nil, bundle: nil)
    }

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

    // MARK: - Lifecycle

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

        setupScrollView()
        setupAvatar()
        setupLabels()
        setupActionButtons()
        setupInfoCard()
        setupTabBar()
        setupMediaGrid()
        setupListContainers()
        setupEmptyState()

        // Back button on view (fixed at top, doesn't scroll)
        backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
        view.addSubview(backButton)

        isMuted = DialogRepository.shared.dialogs[route.publicKey]?.isMuted ?? false
        bindViewModel()
        viewModel.startObservingOnline()
        viewModel.loadSharedContent()
        viewModel.loadCommonGroups()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: false)
    }

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

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

    // MARK: - ViewModel Binding

    private func bindViewModel() {
        viewModel.$isOnline
            .receive(on: DispatchQueue.main)
            .sink { [weak self] online in
                guard let self else { return }
                self.subtitleLabel.text = online ? "online" : "offline"
                self.subtitleLabel.textColor = online ? self.onlineColor : self.textSecondary
            }
            .store(in: &cancellables)

        let dataChanged = viewModel.$mediaItems
            .combineLatest(viewModel.$fileItems, viewModel.$linkItems, viewModel.$commonGroups)
            .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)

        dataChanged
            .sink { [weak self] _ in self?.rebuildTabContent() }
            .store(in: &cancellables)
    }

    private func rebuildTabContent() {
        mediaCollectionView.reloadData()
        view.setNeedsLayout()
    }

    // MARK: - Setup

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

    private func setupAvatar() {
        contentView.addSubview(avatarContainer)

        let dialog = DialogRepository.shared.dialogs[route.publicKey]
        let dn = resolveDisplayName(dialog: dialog)
        let image = AvatarRepository.shared.loadAvatar(publicKey: route.publicKey)

        let av = AvatarView(
            initials: RosettaColors.initials(name: dn, publicKey: route.publicKey),
            colorIndex: RosettaColors.avatarColorIndex(for: dn, publicKey: route.publicKey),
            size: avatarSize, isOnline: false, isSavedMessages: route.isSavedMessages, image: image
        )
        let hosting = UIHostingController(rootView: av)
        hosting.view.backgroundColor = .clear
        addChild(hosting)
        avatarContainer.addSubview(hosting.view)
        hosting.didMove(toParent: self)
        avatarHosting = hosting
    }

    private func setupLabels() {
        let dialog = DialogRepository.shared.dialogs[route.publicKey]
        nameLabel.text = resolveDisplayName(dialog: dialog)
        nameLabel.font = .systemFont(ofSize: 17, weight: .semibold)
        nameLabel.textColor = textPrimary
        nameLabel.textAlignment = .center
        contentView.addSubview(nameLabel)

        subtitleLabel.text = (dialog?.isOnline ?? false) ? "online" : "offline"
        subtitleLabel.font = .systemFont(ofSize: 16)
        subtitleLabel.textColor = textSecondary
        subtitleLabel.textAlignment = .center
        contentView.addSubview(subtitleLabel)
    }

    private func setupActionButtons() {
        var defs: [(icon: String, title: String, action: Selector)] = []
        if showMessageButton || route.isSavedMessages {
            defs.append(("bubble.left.fill", "Message", #selector(messageTapped)))
        }
        if !route.isSavedMessages {
            defs.append(("phone.fill", "Call", #selector(callTapped)))
            defs.append((isMuted ? "bell.slash.fill" : "bell.fill", isMuted ? "Unmute" : "Mute", #selector(muteTapped)))
        }
        defs.append(("magnifyingglass", "Search", #selector(searchTapped)))
        defs.append(("ellipsis", "More", #selector(moreTapped)))

        for def in defs {
            let control = UIControl()
            control.backgroundColor = sectionFill
            control.layer.cornerRadius = buttonCornerRadius
            control.layer.cornerCurve = .continuous
            control.addTarget(self, action: def.action, for: .touchUpInside)

            let iv = UIImageView(image: UIImage(systemName: def.icon))
            iv.contentMode = .scaleAspectFit
            iv.tintColor = primaryBlue
            iv.isUserInteractionEnabled = false
            control.addSubview(iv)

            let lbl = UILabel()
            lbl.text = def.title
            lbl.font = .systemFont(ofSize: 11)
            lbl.textAlignment = .center
            lbl.textColor = primaryBlue
            lbl.isUserInteractionEnabled = false
            control.addSubview(lbl)

            contentView.addSubview(control)
            actionButtonViews.append((control, iv, lbl))
        }
    }

    private func setupInfoCard() {
        infoCard.backgroundColor = sectionFill
        infoCard.layer.cornerRadius = cardCornerRadius
        infoCard.layer.cornerCurve = .continuous
        contentView.addSubview(infoCard)
    }

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

    private func setupMediaGrid() {
        let layout = UICollectionViewCompositionalLayout { _, _ in
            let item = NSCollectionLayoutItem(layoutSize: .init(
                widthDimension: .fractionalWidth(1.0 / 3.0), heightDimension: .fractionalWidth(1.0 / 3.0)))
            let group = NSCollectionLayoutGroup.horizontal(
                layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0 / 3.0)),
                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(ProfileMediaCell.self, forCellWithReuseIdentifier: "m")
        mediaCollectionView.dataSource = self
        mediaCollectionView.delegate = self
        mediaCollectionView.backgroundColor = .clear
        mediaCollectionView.isScrollEnabled = false
        mediaCollectionView.isHidden = true
        contentView.addSubview(mediaCollectionView)
    }

    private func setupListContainers() {
        for c in [filesContainer, linksContainer, groupsContainer] {
            c.backgroundColor = sectionFill
            c.layer.cornerRadius = cardCornerRadius
            c.layer.cornerCurve = .continuous
            c.isHidden = true
            contentView.addSubview(c)
        }
    }

    private func setupEmptyState() {
        emptyIcon.tintColor = textSecondary.withAlphaComponent(0.5)
        emptyIcon.contentMode = .scaleAspectFit
        contentView.addSubview(emptyIcon)
        emptyLabel.font = .systemFont(ofSize: 15, weight: .medium)
        emptyLabel.textColor = textSecondary
        emptyLabel.textAlignment = .center
        contentView.addSubview(emptyLabel)
    }


    // MARK: - Layout

    private func layoutAll() {
        let w = view.bounds.width
        let safeTop = view.safeAreaInsets.top
        scrollView.frame = view.bounds
        contentView.frame.size.width = w

        // Back button fixed on view (not scrollView)
        backButton.frame = CGRect(x: 8, y: safeTop, width: 44, height: 44)

        // Avatar
        var y: CGFloat = safeTop + 15
        avatarContainer.frame = CGRect(x: (w - avatarSize) / 2, y: y, width: avatarSize, height: avatarSize)
        avatarHosting?.view.frame = avatarContainer.bounds
        y += avatarSize + 12

        // Name
        let nameH = nameLabel.sizeThatFits(CGSize(width: w - 60, height: 30)).height
        nameLabel.frame = CGRect(x: 30, y: y, width: w - 60, height: nameH)
        y += nameH + 1

        // Subtitle
        subtitleLabel.frame = CGRect(x: 30, y: y, width: w - 60, height: 20)
        y += 20 + 20

        // ── Buttons (both states) ──
        let btnCount = CGFloat(actionButtonViews.count)
        let btnW = (w - hPad * 2 - buttonSpacing * (btnCount - 1)) / btnCount
        for (i, (c, iv, lbl)) in actionButtonViews.enumerated() {
            c.frame = CGRect(x: hPad + CGFloat(i) * (btnW + buttonSpacing), y: y, width: btnW, height: 58)
            iv.frame = CGRect(x: (btnW - 24) / 2, y: 7, width: 24, height: 26)
            lbl.frame = CGRect(x: 0, y: 37, width: btnW, height: 16)
        }
        y += 58 + 16

        // Info card
        let infoH = layoutInfoCard(width: w - hPad * 2)
        infoCard.frame = CGRect(x: hPad, y: y, width: w - hPad * 2, height: infoH)
        y += infoH + 20

        // Tab bar
        tabBar.frame = CGRect(x: hPad, y: y, width: w - hPad * 2, height: 38)
        y += 38 + 12

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

        contentView.frame.size.height = y
        scrollView.contentSize = CGSize(width: w, height: y)
    }


    private func layoutInfoCard(width: CGFloat) -> CGFloat {
        infoCard.subviews.forEach { $0.removeFromSuperview() }
        let dialog = DialogRepository.shared.dialogs[route.publicKey]
        let username = dialog?.opponentUsername ?? route.username
        var cy: CGFloat = 0

        if !username.isEmpty {
            cy += makeInfoRow(in: infoCard, at: cy, width: width,
                              label: "username", value: "@\(username)", rawValue: username, fieldId: "username")
            let div = UIView()
            div.backgroundColor = separatorColor
            div.frame = CGRect(x: 16, y: cy, width: width - 16, height: 1 / UIScreen.main.scale)
            infoCard.addSubview(div)
        }
        cy += makeInfoRow(in: infoCard, at: cy, width: width,
                          label: "public key", value: route.publicKey, rawValue: route.publicKey, fieldId: "publicKey")
        return cy
    }

    private func makeInfoRow(in card: UIView, at y: CGFloat, width: CGFloat,
                             label: String, value: String, rawValue: String, fieldId: String) -> CGFloat {
        let row = UIControl()
        row.frame = CGRect(x: 0, y: y, width: width, height: 66)
        row.addAction(UIAction { [weak self] _ in
            UIPasteboard.general.string = rawValue
            self?.showCopied(fieldId: fieldId)
        }, for: .touchUpInside)

        let titleLbl = UILabel()
        titleLbl.text = label
        titleLbl.font = .systemFont(ofSize: 14)
        titleLbl.textColor = textSecondary
        titleLbl.frame = CGRect(x: 16, y: 12, width: width - 32, height: 18)
        titleLbl.isUserInteractionEnabled = false
        row.addSubview(titleLbl)

        let isCopied = copiedField == fieldId
        let valueLbl = UILabel()
        valueLbl.text = isCopied ? "Copied" : value
        valueLbl.font = .systemFont(ofSize: 17)
        valueLbl.textColor = isCopied ? onlineColor : primaryBlue
        valueLbl.lineBreakMode = .byTruncatingMiddle
        valueLbl.frame = CGRect(x: 16, y: 32, width: width - 32, height: 22)
        valueLbl.isUserInteractionEnabled = false
        valueLbl.tag = fieldId.hashValue
        row.addSubview(valueLbl)

        card.addSubview(row)
        return 66
    }

    private func showCopied(fieldId: String) {
        copiedField = fieldId
        let infoH = layoutInfoCard(width: infoCard.bounds.width)
        infoCard.frame.size.height = infoH
        Task { @MainActor in
            try? await Task.sleep(for: .seconds(1.5))
            guard copiedField == fieldId else { return }
            copiedField = nil
            let h = layoutInfoCard(width: infoCard.bounds.width)
            infoCard.frame.size.height = h
        }
    }

    // MARK: - Tab Content Layout

    private func layoutTabContent(at startY: CGFloat, width: CGFloat) -> CGFloat {
        mediaCollectionView.isHidden = true
        filesContainer.isHidden = true
        linksContainer.isHidden = true
        groupsContainer.isHidden = true
        emptyIcon.isHidden = true
        emptyLabel.isHidden = true

        var y = startY

        switch selectedTab {
        case 0: // Media
            if viewModel.mediaItems.isEmpty {
                y = layoutEmpty(at: y, width: width, icon: "photo.on.rectangle", title: "No Media Yet")
            } else {
                mediaCollectionView.isHidden = false
                let rows = ceil(Double(viewModel.mediaItems.count) / 3.0)
                let itemH = (width - 2) / 3
                let gridH = CGFloat(rows) * itemH + max(0, CGFloat(rows - 1))
                mediaCollectionView.frame = CGRect(x: 0, y: y, width: width, height: gridH)
                y += gridH + 40
            }
        case 1: // Files
            if viewModel.fileItems.isEmpty {
                y = layoutEmpty(at: y, width: width, icon: "doc", title: "No Files Yet")
            } else {
                filesContainer.isHidden = false
                let h = layoutFileRows(width: width - hPad * 2)
                filesContainer.frame = CGRect(x: hPad, y: y, width: width - hPad * 2, height: h)
                y += h + 40
            }
        case 2: // Links
            if viewModel.linkItems.isEmpty {
                y = layoutEmpty(at: y, width: width, icon: "link", title: "No Links Yet")
            } else {
                linksContainer.isHidden = false
                let h = layoutLinkRows(width: width - hPad * 2)
                linksContainer.frame = CGRect(x: hPad, y: y, width: width - hPad * 2, height: h)
                y += h + 40
            }
        case 3: // Groups
            if viewModel.commonGroups.isEmpty {
                y = layoutEmpty(at: y, width: width, icon: "person.2", title: "No Groups in Common")
            } else {
                groupsContainer.isHidden = false
                let h = layoutGroupRows(width: width - hPad * 2)
                groupsContainer.frame = CGRect(x: hPad, y: y, width: width - hPad * 2, height: h)
                y += h + 40
            }
        default: break
        }
        return y
    }

    private func layoutEmpty(at y: CGFloat, width: CGFloat, icon: String, title: String) -> CGFloat {
        emptyIcon.isHidden = false
        emptyLabel.isHidden = false
        emptyIcon.image = UIImage(systemName: icon)
        emptyLabel.text = title
        emptyIcon.frame = CGRect(x: (width - 50) / 2, y: y + 20, width: 50, height: 50)
        emptyLabel.frame = CGRect(x: 30, y: y + 82, width: width - 60, height: 20)
        return y + 142
    }

    private func layoutFileRows(width: CGFloat) -> CGFloat {
        filesContainer.subviews.forEach { $0.removeFromSuperview() }
        var cy: CGFloat = 0
        for (i, file) in viewModel.fileItems.enumerated() {
            let rowH: CGFloat = 60
            let row = UIView(frame: CGRect(x: 0, y: cy, width: width, height: rowH))

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

            let icon = UIImageView(image: UIImage(systemName: "doc.fill"))
            icon.tintColor = primaryBlue
            icon.contentMode = .scaleAspectFit
            icon.frame = CGRect(x: 9, y: 9, width: 22, height: 22)
            iconBg.addSubview(icon)

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

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

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

    private func layoutLinkRows(width: CGFloat) -> CGFloat {
        linksContainer.subviews.forEach { $0.removeFromSuperview() }
        var cy: CGFloat = 0
        for (i, link) in viewModel.linkItems.enumerated() {
            let rowH: CGFloat = 60
            let row = UIControl(frame: CGRect(x: 0, y: cy, width: width, height: rowH))
            row.addAction(UIAction { _ in
                if let url = URL(string: link.url) { UIApplication.shared.open(url) }
            }, for: .touchUpInside)

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

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

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

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

    private func layoutGroupRows(width: CGFloat) -> CGFloat {
        groupsContainer.subviews.forEach { $0.removeFromSuperview() }
        var cy: CGFloat = 0
        for (i, group) in viewModel.commonGroups.enumerated() {
            let rowH: CGFloat = 56
            let row = UIView(frame: CGRect(x: 0, y: cy, width: width, height: rowH))

            let av = AvatarView(
                initials: RosettaColors.initials(name: group.title, publicKey: group.dialogKey),
                colorIndex: RosettaColors.avatarColorIndex(for: group.title, publicKey: group.dialogKey),
                size: 40, isOnline: false, image: group.avatar)
            let h = UIHostingController(rootView: av)
            h.view.backgroundColor = .clear
            h.view.frame = CGRect(x: 16, y: 8, width: 40, height: 40)
            row.addSubview(h.view)

            let title = UILabel(frame: CGRect(x: 68, y: (rowH - 22) / 2, width: width - 84, height: 22))
            title.text = group.title
            title.font = .systemFont(ofSize: 17)
            title.textColor = textPrimary
            row.addSubview(title)

            groupsContainer.addSubview(row)
            cy += rowH
            if i < viewModel.commonGroups.count - 1 {
                let sep = UIView(frame: CGRect(x: 68, y: cy, width: width - 68, height: 1 / UIScreen.main.scale))
                sep.backgroundColor = separatorColor
                groupsContainer.addSubview(sep)
            }
        }
        return cy
    }

    // MARK: - UICollectionView (Media Grid)

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

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

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

    // MARK: - ProfileTabBarDelegate

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

    // MARK: - Actions

    @objc private func backTapped() { navigationController?.popViewController(animated: true) }

    @objc private func callTapped() {
        let dialog = DialogRepository.shared.dialogs[route.publicKey]
        let name = dialog?.opponentTitle ?? route.title
        let user = dialog?.opponentUsername ?? route.username
        Task { @MainActor in
            _ = CallManager.shared.startOutgoingCall(toPublicKey: route.publicKey, title: name, username: user)
        }
    }

    @objc private func muteTapped() {
        DialogRepository.shared.toggleMute(opponentKey: route.publicKey)
        isMuted.toggle()
        // Find mute button and update
        for (_, iv, lbl) in actionButtonViews where lbl.text == "Mute" || lbl.text == "Unmute" {
            iv.image = UIImage(systemName: isMuted ? "bell.slash.fill" : "bell.fill")
            lbl.text = isMuted ? "Unmute" : "Mute"
        }
    }

    @objc private func searchTapped() { navigationController?.popViewController(animated: true) }

    @objc private func moreTapped() {
        let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        if !route.isSavedMessages {
            sheet.addAction(UIAlertAction(title: "Block User", style: .destructive))
        }
        sheet.addAction(UIAlertAction(title: "Clear Chat History", style: .destructive))
        if let ctx = groupContext, ctx.isAdmin {
            sheet.addAction(UIAlertAction(title: "Remove from Group", style: .destructive) { [weak self] _ in
                self?.showRemoveFromGroupAlert()
            })
        }
        sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(sheet, animated: true)
    }

    private func showRemoveFromGroupAlert() {
        guard let ctx = groupContext else { return }
        let name = route.title.isEmpty ? String(route.publicKey.prefix(12)) : route.title
        let alert = UIAlertController(
            title: "Remove Member",
            message: "Remove \(name) from this group?",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Remove", style: .destructive) { [weak self] _ in
            guard let self else { return }
            Task {
                do {
                    try await GroupService.shared.kickMember(
                        groupDialogKey: ctx.dialogKey,
                        memberPublicKey: self.route.publicKey
                    )
                    NotificationCenter.default.post(
                        name: .groupMemberKicked,
                        object: nil,
                        userInfo: ["groupDialogKey": ctx.dialogKey, "memberKey": self.route.publicKey]
                    )
                    await MainActor.run {
                        self.navigationController?.popViewController(animated: true)
                    }
                } catch {
                    await MainActor.run {
                        let errAlert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
                        errAlert.addAction(UIAlertAction(title: "OK", style: .cancel))
                        self.present(errAlert, animated: true)
                    }
                }
            }
        })
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }

    @objc private func messageTapped() {
        if route.isSavedMessages {
            navigationController?.popViewController(animated: true)
        } else {
            let chatVC = ChatDetailViewController(route: route)
            navigationController?.pushViewController(chatVC, animated: true)
        }
    }

    // MARK: - Helpers

    private func resolveDisplayName(dialog: Dialog?) -> String {
        if let d = dialog, !d.opponentTitle.isEmpty { return d.opponentTitle }
        if !route.title.isEmpty { return route.title }
        if let d = dialog, !d.opponentUsername.isEmpty { return "@\(d.opponentUsername)" }
        if !route.username.isEmpty { return "@\(route.username)" }
        return String(route.publicKey.prefix(12))
    }

    // MARK: - Swipe Back

    private func setupFullWidthSwipeBack() {
        guard !addedSwipeBackGesture else { return }
        addedSwipeBackGesture = true
        guard let nav = navigationController,
              let edge = nav.interactivePopGestureRecognizer,
              let targets = edge.value(forKey: "targets") as? NSArray, targets.count > 0 else { return }
        edge.isEnabled = true
        let pan = UIPanGestureRecognizer()
        pan.setValue(targets, forKey: "targets")
        pan.delegate = self
        nav.view.addGestureRecognizer(pan)
    }

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

// MARK: - Media Cell

private final class ProfileMediaCell: UICollectionViewCell {
    private let iv = UIImageView()
    override init(frame: CGRect) {
        super.init(frame: frame)
        iv.contentMode = .scaleAspectFill
        iv.clipsToBounds = true
        contentView.addSubview(iv)
        contentView.backgroundColor = UIColor(white: 0.15, alpha: 1)
    }
    @available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
    override func layoutSubviews() { super.layoutSubviews(); iv.frame = contentView.bounds }
    override func prepareForReuse() { super.prepareForReuse(); iv.image = nil }
    func configure(with item: SharedMediaItem) {
        if let img = AttachmentCache.shared.loadImage(forAttachmentId: item.attachmentId) { iv.image = img }
        else if !item.blurhash.isEmpty, let blur = BlurHashDecoder.decode(blurHash: item.blurhash, width: 32, height: 32) { iv.image = blur }
    }
}
