//
//  WildPhotographerReportListViewController.swift
//  suixinkan
//

import Kingfisher
import SnapKit
import UIKit

#if targetEnvironment(simulator)
import MapKit
#else
import MAMapKit
#endif

/// 我的举报列表页，支持按状态筛选并进入举报详情。
final class WildPhotographerReportListViewController: BaseViewController {
    private let viewModel: WildPhotographerReportListViewModel
    private let api: any WildPhotographerReportServing
    private let scrollView = UIScrollView()
    private let stack = UIStackView()
    private let filterTabBar = WildReportFilterTabBarView()

    init(
        homeViewModel: WildPhotographerReportHomeViewModel,
        api: (any WildPhotographerReportServing)? = nil
    ) {
        self.viewModel = WildPhotographerReportListViewModel(homeViewModel: homeViewModel)
        self.api = api ?? NetworkServices.shared.wildPhotographerReportAPI
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func setupNavigationBar() {
        title = "我的举报"
    }

    override func setupUI() {
        view.backgroundColor = AppColor.pageBackgroundSoft
        scrollView.showsVerticalScrollIndicator = false
        scrollView.alwaysBounceVertical = true
        scrollView.delegate = self
        stack.axis = .vertical
        stack.spacing = 14

        filterTabBar.onSelect = { [weak self] filter in
            guard let self else { return }
            Task {
                await self.viewModel.selectFilter(
                    filter,
                    api: self.api,
                    scenicId: self.currentScenicId
                )
            }
        }

        view.addSubview(filterTabBar)
        view.addSubview(scrollView)
        scrollView.addSubview(stack)
        reloadContent(animated: false)
    }

    override func setupConstraints() {
        filterTabBar.snp.makeConstraints { make in
            make.top.equalTo(view.safeAreaLayoutGuide).offset(14)
            make.leading.trailing.equalToSuperview().inset(18)
        }
        scrollView.snp.makeConstraints { make in
            make.top.equalTo(filterTabBar.snp.bottom).offset(14)
            make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
        }
        stack.snp.makeConstraints { make in
            make.top.equalTo(scrollView.contentLayoutGuide)
            make.leading.equalTo(scrollView.contentLayoutGuide).offset(18)
            make.trailing.equalTo(scrollView.contentLayoutGuide).inset(18)
            make.bottom.equalTo(scrollView.contentLayoutGuide).inset(24)
            make.width.equalTo(scrollView.frameLayoutGuide).offset(-36)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            await viewModel.loadInitial(api: api, scenicId: currentScenicId)
        }
    }

    override func bindActions() {
        viewModel.onStateChange = { [weak self] in
            Task { @MainActor in self?.reloadContent(animated: true) }
        }
        viewModel.onShowMessage = { [weak self] message in
            Task { @MainActor in self?.showToast(message) }
        }
    }

    @MainActor
    private func reloadContent(animated: Bool) {
        let changes: () -> Void = { [weak self] in
            self?.rebuildContent()
        }
        guard animated else {
            changes()
            return
        }
        UIView.transition(
            with: stack,
            duration: 0.22,
            options: [.transitionCrossDissolve, .allowUserInteraction],
            animations: changes
        )
    }

    private func rebuildContent() {
        filterTabBar.apply(selectedFilter: viewModel.selectedFilter)

        stack.arrangedSubviews.forEach { view in
            stack.removeArrangedSubview(view)
            view.removeFromSuperview()
        }

        let reports = viewModel.filteredReports
        if reports.isEmpty {
            stack.addArrangedSubview(viewModel.isLoading ? WildReportListLoadingView(text: "正在加载举报记录...") : WildReportListEmptyView())
        } else {
            reports.forEach { record in
                let card = WildReportListCardView(record: record)
                card.onOpen = { [weak self] in self?.openDetail(record) }
                card.onShare = { [weak self] in self?.shareReport(record) }
                stack.addArrangedSubview(card)
            }
        }

        let footer = WildReportUI.label(viewModel.footerDisplayText, font: .app(.caption), color: UIColor(hex: 0x9CA3AF), lines: 0)
        footer.textAlignment = .center
        let footerWrap = UIView()
        footerWrap.addSubview(footer)
        footer.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(8)
            make.leading.trailing.bottom.equalToSuperview()
        }
        stack.addArrangedSubview(footerWrap)
    }

    private func openDetail(_ record: WildReportRecord) {
        navigationController?.pushViewController(
            WildPhotographerReportDetailViewController(record: record, homeViewModel: viewModel.homeViewModel, api: api),
            animated: true
        )
    }

    private func shareReport(_ record: WildReportRecord) {
        showLoading()
        Task { [weak self] in
            guard let self else { return }
            let payload = await self.viewModel.shareReport(record, api: self.api)
            await MainActor.run {
                self.hideLoading()
                guard let payload else { return }
                WeChatShareService.shareMiniProgram(payload, previewImage: WildReportShareCover.image) { [weak self] result in
                    self?.showWildReportShareResult(result)
                }
            }
        }
    }

    private var currentScenicId: Int? {
        let scenicId = AppStore.shared.session.currentScenicId
        return scenicId > 0 ? scenicId : nil
    }
}

extension WildPhotographerReportListViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let threshold: CGFloat = 80
        let visibleBottom = scrollView.contentOffset.y + scrollView.bounds.height
        let triggerY = scrollView.contentSize.height - threshold
        guard visibleBottom >= triggerY, scrollView.contentSize.height > scrollView.bounds.height else { return }
        Task {
            await viewModel.loadMoreIfNeeded(api: api, scenicId: currentScenicId)
        }
    }
}

/// 举报摄影师微信分享封面资源。
private enum WildReportShareCover {
    static var image: UIImage? {
        UIImage(named: "report_share_cover")
    }
}

/// 我的举报筛选栏，使用四段等宽胶囊按钮展示状态过滤。
private final class WildReportFilterTabBarView: UIView {
    var onSelect: ((WildReportListFilter) -> Void)?
    private let stack = UIStackView()
    private var buttons: [WildReportListFilter: UIButton] = [:]

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        layer.cornerRadius = 12
        layer.borderWidth = 1
        layer.borderColor = AppColor.cardOutline.cgColor

        stack.axis = .horizontal
        stack.spacing = 0
        stack.distribution = .fillEqually
        addSubview(stack)
        stack.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(4)
        }

        WildReportListFilter.allCases.forEach { filter in
            let button = UIButton(type: .system)
            button.setTitle(filter.rawValue, for: .normal)
            button.titleLabel?.adjustsFontSizeToFitWidth = true
            button.titleLabel?.minimumScaleFactor = 0.85
            button.layer.cornerRadius = 8
            button.addTarget(self, action: #selector(filterTapped(_:)), for: .touchUpInside)
            buttons[filter] = button
            stack.addArrangedSubview(button)
        }

        snp.makeConstraints { make in
            make.height.equalTo(48)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func apply(selectedFilter: WildReportListFilter) {
        buttons.forEach { filter, button in
            let selected = filter == selectedFilter
            button.backgroundColor = selected ? UIColor(hex: 0xEAF3FF) : .clear
            button.setTitleColor(selected ? AppColor.primary : UIColor(hex: 0x6B7280), for: .normal)
            button.titleLabel?.font = .systemFont(ofSize: 13, weight: selected ? .bold : .medium)
        }
    }

    @objc private func filterTapped(_ sender: UIButton) {
        guard let filter = buttons.first(where: { $0.value == sender })?.key else { return }
        onSelect?(filter)
    }
}

/// 我的举报列表卡片，展示一条举报记录并提供详情与分享入口。
private final class WildReportListCardView: UIControl, UIGestureRecognizerDelegate {
    var onOpen: (() -> Void)?
    var onShare: (() -> Void)?

    private let record: WildReportRecord
    private let scenicName: String
    private let detailAddress: String
    private let statusColor: UIColor

    init(record: WildReportRecord) {
        self.record = record
        let location = WildReportLocationParser.split(record.location)
        self.scenicName = location.scenic
        self.detailAddress = location.detail
        self.statusColor = record.status.displayColor
        super.init(frame: .zero)
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isHighlighted: Bool {
        didSet {
            alpha = isHighlighted ? 0.82 : 1
        }
    }

    private func setupUI() {
        backgroundColor = .white
        layer.cornerRadius = 16
        layer.borderWidth = 1
        layer.borderColor = AppColor.cardOutline.cgColor

        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(openTapped))
        tapRecognizer.delegate = self
        tapRecognizer.cancelsTouchesInView = false
        addGestureRecognizer(tapRecognizer)

        let contentStack = UIStackView()
        contentStack.axis = .vertical
        contentStack.spacing = 13
        addSubview(contentStack)
        contentStack.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(15)
        }

        contentStack.addArrangedSubview(makeHeader())
        contentStack.addArrangedSubview(makeMetaRows())
        contentStack.addArrangedSubview(makeLocationRow())
    }

    private func makeHeader() -> UIView {
        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .top
        row.spacing = 12

        let iconBox = UIView()
        iconBox.backgroundColor = statusColor.withAlphaComponent(0.12)
        iconBox.layer.cornerRadius = 12
        let icon = UIImageView(image: UIImage(systemName: "camera.fill"))
        icon.tintColor = statusColor
        icon.contentMode = .scaleAspectFit
        iconBox.addSubview(icon)
        icon.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(20)
        }
        iconBox.snp.makeConstraints { make in
            make.size.equalTo(44)
        }
        row.addArrangedSubview(iconBox)

        let textStack = UIStackView()
        textStack.axis = .vertical
        textStack.spacing = 8
        let titleStatusRow = UIStackView()
        titleStatusRow.axis = .horizontal
        titleStatusRow.alignment = .top
        titleStatusRow.spacing = 8

        let titleLabel = WildReportUI.label(record.title, font: .app(.title), color: AppColor.textPrimary, lines: 2)
        titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        titleStatusRow.addArrangedSubview(titleLabel)
        titleStatusRow.addArrangedSubview(makeStatusAndShareGroup())
        textStack.addArrangedSubview(titleStatusRow)
        textStack.addArrangedSubview(makeScenicPill())
        row.addArrangedSubview(textStack)
        return row
    }

    private func makeStatusAndShareGroup() -> UIView {
        let group = UIStackView()
        group.axis = .horizontal
        group.alignment = .center
        group.spacing = 6
        group.setContentHuggingPriority(.required, for: .horizontal)

        let status = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
        status.text = record.status.rawValue
        status.font = .systemFont(ofSize: 11, weight: .semibold)
        status.textColor = statusColor
        status.textAlignment = .center
        status.backgroundColor = statusColor.withAlphaComponent(0.12)
        status.layer.cornerRadius = 12
        status.clipsToBounds = true
        status.setContentHuggingPriority(.required, for: .horizontal)
        status.setContentCompressionResistancePriority(.required, for: .horizontal)
        status.snp.makeConstraints { make in
            make.height.equalTo(24)
        }

        let shareButton = UIButton(type: .system)
        shareButton.backgroundColor = AppColor.primaryLight
        shareButton.layer.cornerRadius = 14
        shareButton.tintColor = AppColor.primary
        shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
        shareButton.accessibilityLabel = "分享举报"
        shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)
        shareButton.snp.makeConstraints { make in
            make.size.equalTo(28)
        }

        group.addArrangedSubview(status)
        group.addArrangedSubview(shareButton)
        return group
    }

    private func makeScenicPill() -> UIView {
        let wrapper = UIView()
        let label = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8))
        label.text = scenicName
        label.font = .systemFont(ofSize: 12, weight: .semibold)
        label.textColor = AppColor.primary
        label.backgroundColor = AppColor.primary.withAlphaComponent(0.10)
        label.layer.cornerRadius = 11
        label.clipsToBounds = true
        label.setContentHuggingPriority(.required, for: .horizontal)
        label.setContentCompressionResistancePriority(.required, for: .horizontal)
        wrapper.addSubview(label)
        label.snp.makeConstraints { make in
            make.top.bottom.leading.equalToSuperview()
            make.trailing.lessThanOrEqualToSuperview()
            make.height.equalTo(22)
        }
        return wrapper
    }

    private func makeMetaRows() -> UIView {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 8
        stack.addArrangedSubview(WildReportListMetaRowView(title: "举报编号", value: record.id))
        stack.addArrangedSubview(WildReportListMetaRowView(title: "举报时间", value: record.submitTime))
        return stack
    }

    private func makeLocationRow() -> UIView {
        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .center
        row.spacing = 6
        row.layoutMargins = UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)
        row.isLayoutMarginsRelativeArrangement = true

        let icon = UIImageView(image: UIImage(systemName: "mappin.and.ellipse"))
        icon.tintColor = UIColor(hex: 0x9CA3AF)
        icon.contentMode = .scaleAspectFit
        icon.snp.makeConstraints { make in
            make.size.equalTo(14)
        }
        let addressLabel = WildReportUI.label(detailAddress, font: .app(.caption), color: AppColor.textSecondary)
        addressLabel.lineBreakMode = .byTruncatingTail
        let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
        chevron.tintColor = UIColor(hex: 0xC4CAD4)
        chevron.contentMode = .scaleAspectFit
        chevron.snp.makeConstraints { make in
            make.size.equalTo(13)
        }

        row.addArrangedSubview(icon)
        row.addArrangedSubview(addressLabel)
        row.addArrangedSubview(UIView())
        row.addArrangedSubview(chevron)
        return row
    }

    @objc private func openTapped() {
        onOpen?()
    }

    @objc private func shareTapped() {
        onShare?()
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        var currentView: UIView? = touch.view
        while let view = currentView {
            if view is UIButton {
                return false
            }
            currentView = view.superview
        }
        return true
    }
}

/// 我的举报列表信息行，展示举报编号、举报时间等左右结构内容。
private final class WildReportListMetaRowView: UIView {
    init(title: String, value: String) {
        super.init(frame: .zero)

        let titleLabel = WildReportUI.label(title, font: .app(.caption), color: UIColor(hex: 0x9CA3AF))
        let valueLabel = WildReportUI.label(value, font: .systemFont(ofSize: 13, weight: .medium), color: AppColor.textPrimary)
        valueLabel.textAlignment = .right
        valueLabel.adjustsFontSizeToFitWidth = true
        valueLabel.minimumScaleFactor = 0.8

        addSubview(titleLabel)
        addSubview(valueLabel)
        titleLabel.snp.makeConstraints { make in
            make.leading.top.bottom.equalToSuperview()
        }
        valueLabel.snp.makeConstraints { make in
            make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12)
            make.trailing.top.bottom.equalToSuperview()
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 我的举报列表空态卡片。
private final class WildReportListEmptyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        layer.cornerRadius = 16

        let stack = UIStackView()
        stack.axis = .vertical
        stack.alignment = .center
        stack.spacing = 10
        let icon = UIImageView(image: UIImage(systemName: "doc.text.magnifyingglass"))
        icon.tintColor = UIColor(hex: 0xB6BECA)
        icon.contentMode = .scaleAspectFit
        icon.snp.makeConstraints { make in
            make.size.equalTo(36)
        }
        let label = WildReportUI.label("暂无相关举报记录", font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textSecondary)
        stack.addArrangedSubview(icon)
        stack.addArrangedSubview(label)

        addSubview(stack)
        stack.snp.makeConstraints { make in
            make.top.bottom.equalToSuperview().inset(48)
            make.leading.trailing.equalToSuperview().inset(16)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 我的举报列表加载态卡片。
private final class WildReportListLoadingView: UIView {
    init(text: String) {
        super.init(frame: .zero)
        backgroundColor = .white
        layer.cornerRadius = 16

        let indicator = UIActivityIndicatorView(style: .medium)
        indicator.color = AppColor.primary
        indicator.startAnimating()
        let label = WildReportUI.label(text, font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textSecondary)

        let stack = UIStackView(arrangedSubviews: [indicator, label])
        stack.axis = .vertical
        stack.alignment = .center
        stack.spacing = 12
        addSubview(stack)
        stack.snp.makeConstraints { make in
            make.top.bottom.equalToSuperview().inset(48)
            make.leading.trailing.equalToSuperview().inset(16)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 举报详情页。
final class WildPhotographerReportDetailViewController: BaseViewController {
    private let viewModel: WildPhotographerReportDetailViewModel
    private let homeViewModel: WildPhotographerReportHomeViewModel
    private let api: any WildPhotographerReportServing
    private let scrollView = UIScrollView()
    private let stack = UIStackView()
    private let bottomBar = UIView()
    private let bottomButtonRow = UIStackView()

    init(
        record: WildReportRecord,
        homeViewModel: WildPhotographerReportHomeViewModel,
        api: (any WildPhotographerReportServing)? = nil
    ) {
        self.viewModel = WildPhotographerReportDetailViewModel(record: record, homeViewModel: homeViewModel)
        self.homeViewModel = homeViewModel
        self.api = api ?? NetworkServices.shared.wildPhotographerReportAPI
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func setupNavigationBar() {
        title = "举报详情"
    }

    override func setupUI() {
        view.backgroundColor = AppColor.pageBackgroundSoft
        scrollView.showsVerticalScrollIndicator = false
        scrollView.alwaysBounceVertical = true
        stack.axis = .vertical
        stack.spacing = 12

        bottomBar.backgroundColor = UIColor.white.withAlphaComponent(0.96)
        bottomButtonRow.axis = .horizontal
        bottomButtonRow.alignment = .fill
        bottomButtonRow.distribution = .fillEqually
        bottomButtonRow.spacing = 12

        let supplementButton = makeBottomButton(title: "补充证据", iconName: "doc.badge.plus", isPrimary: false)
        supplementButton.addTarget(self, action: #selector(supplementTapped), for: .touchUpInside)
        let mapButton = makeBottomButton(title: "查看附近风险地图", iconName: "map.fill", isPrimary: true)
        mapButton.addTarget(self, action: #selector(mapTapped), for: .touchUpInside)
        bottomButtonRow.addArrangedSubview(supplementButton)
        bottomButtonRow.addArrangedSubview(mapButton)

        view.addSubview(scrollView)
        view.addSubview(bottomBar)
        scrollView.addSubview(stack)
        bottomBar.addSubview(bottomButtonRow)

        let topBorder = UIView()
        topBorder.backgroundColor = AppColor.cardOutline
        bottomBar.addSubview(topBorder)
        topBorder.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.height.equalTo(0.5)
        }

        rebuildContent()
    }

    override func setupConstraints() {
        scrollView.snp.makeConstraints { make in
            make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
            make.bottom.equalToSuperview()
        }
        stack.snp.makeConstraints { make in
            make.top.equalTo(scrollView.contentLayoutGuide).offset(14)
            make.leading.equalTo(scrollView.contentLayoutGuide).offset(18)
            make.trailing.equalTo(scrollView.contentLayoutGuide).inset(18)
            make.bottom.equalTo(scrollView.contentLayoutGuide).inset(120)
            make.width.equalTo(scrollView.frameLayoutGuide).offset(-36)
        }
        bottomBar.snp.makeConstraints { make in
            make.leading.trailing.bottom.equalToSuperview()
        }
        bottomButtonRow.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(10)
            make.leading.trailing.equalToSuperview().inset(16)
            make.bottom.equalTo(view.safeAreaLayoutGuide).inset(12)
            make.height.equalTo(52)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            await viewModel.loadDetailIfNeeded(api: api)
        }
    }

    override func bindActions() {
        viewModel.onStateChange = { [weak self] in
            Task { @MainActor in self?.rebuildContent() }
        }
        viewModel.onShowMessage = { [weak self] message in
            Task { @MainActor in self?.showToast(message) }
        }
        homeViewModel.onStateChange = { [weak self] in
            Task { @MainActor in self?.rebuildContent() }
        }
    }

    @MainActor
    private func rebuildContent() {
        stack.arrangedSubviews.forEach { view in
            stack.removeArrangedSubview(view)
            view.removeFromSuperview()
        }
        stack.addArrangedSubview(makeSummaryCard())
        stack.addArrangedSubview(makeReportInfoCard())
        if viewModel.showHandlerInfo, let info = viewModel.handlerInfo {
            stack.addArrangedSubview(makeHandlerCard(info: info))
        }
        stack.addArrangedSubview(makeEvidenceCard())
        if !viewModel.supplementaryEvidences.isEmpty {
            stack.addArrangedSubview(makeSupplementCard())
        }
        stack.addArrangedSubview(makeTimelineCard())
    }

    private func makeSummaryCard() -> UIView {
        let card = WildReportCardView(spacing: 20, inset: 18)
        card.layer.cornerRadius = 18

        let topRow = UIStackView()
        topRow.axis = .horizontal
        topRow.alignment = .center
        topRow.spacing = 14

        let iconBox = UIView()
        iconBox.backgroundColor = AppColor.primary
        iconBox.layer.cornerRadius = 16
        let icon = UIImageView(image: UIImage(systemName: "exclamationmark.shield.fill"))
        icon.tintColor = .white
        icon.contentMode = .scaleAspectFit
        iconBox.addSubview(icon)
        icon.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(30)
        }
        iconBox.snp.makeConstraints { make in
            make.size.equalTo(62)
        }

        let titleStack = UIStackView()
        titleStack.axis = .vertical
        titleStack.spacing = 8
        let titleLabel = WildReportUI.label(viewModel.heroTitle, font: .systemFont(ofSize: 23, weight: .heavy), color: AppColor.textPrimary, lines: 1)
        titleLabel.adjustsFontSizeToFitWidth = true
        titleLabel.minimumScaleFactor = 0.82
        titleStack.addArrangedSubview(titleLabel)
        titleStack.addArrangedSubview(makeScenicPill())

        let shareButton = WildReportDetailIconTextButton(title: "分享", iconName: "square.and.arrow.up")
        shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)

        topRow.addArrangedSubview(iconBox)
        topRow.addArrangedSubview(titleStack)
        topRow.addArrangedSubview(shareButton)
        titleStack.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        shareButton.snp.makeConstraints { make in
            make.size.equalTo(48)
        }

        card.stack.addArrangedSubview(topRow)
        card.stack.addArrangedSubview(makeDivider())

        let bottomRow = UIStackView()
        bottomRow.axis = .horizontal
        bottomRow.alignment = .center
        bottomRow.spacing = 12

        let idStack = UIStackView()
        idStack.axis = .vertical
        idStack.spacing = 4
        idStack.addArrangedSubview(WildReportUI.label("举报编号", font: .app(.body), color: AppColor.textSecondary))
        idStack.addArrangedSubview(WildReportUI.label(viewModel.record.id, font: .systemFont(ofSize: 19, weight: .semibold), color: AppColor.textPrimary))

        let statusLabel = PaddingLabel(insets: UIEdgeInsets(top: 0, left: 14, bottom: 0, right: 14))
        statusLabel.text = viewModel.displayStatusText
        statusLabel.font = .systemFont(ofSize: 16, weight: .bold)
        statusLabel.textColor = viewModel.record.status.displayColor
        statusLabel.backgroundColor = viewModel.record.status.displayColor.withAlphaComponent(0.12)
        statusLabel.textAlignment = .center
        statusLabel.layer.cornerRadius = 19
        statusLabel.clipsToBounds = true
        statusLabel.snp.makeConstraints { make in
            make.height.equalTo(38)
        }

        bottomRow.addArrangedSubview(idStack)
        bottomRow.addArrangedSubview(UIView())
        bottomRow.addArrangedSubview(statusLabel)
        card.stack.addArrangedSubview(bottomRow)
        return card
    }

    private func makeReportInfoCard() -> UIView {
        let card = WildReportCardView(spacing: 0, inset: 0)
        card.layer.cornerRadius = 18
        card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "举报类型", value: viewModel.record.reportType.label))
        card.stack.addArrangedSubview(makeInsetDivider())
        card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "举报说明", value: viewModel.descriptionText, multiline: true))
        card.stack.addArrangedSubview(makeInsetDivider())
        card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "微信号/手机号", value: viewModel.contactText))
        if !viewModel.handleRemarkText.isEmpty, !viewModel.showHandlerInfo {
            card.stack.addArrangedSubview(makeInsetDivider())
            card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理备注", value: viewModel.handleRemarkText, multiline: true))
        }
        return card
    }

    private func makeEvidenceCard() -> UIView {
        let card = WildReportCardView(spacing: 16, inset: 18)
        card.layer.cornerRadius = 18
        if !viewModel.reporterMediaItems.isEmpty {
            card.stack.addArrangedSubview(makeReporterMaterialsSection())
            card.stack.addArrangedSubview(makeDivider())
        }
        if !viewModel.paymentScreenshotItems.isEmpty {
            card.stack.addArrangedSubview(makePaymentScreenshotSection())
            card.stack.addArrangedSubview(makeDivider())
        }
        card.stack.addArrangedSubview(makeLocationSection())
        return card
    }

    private func makeHandlerCard(info: WildReportHandlerInfo) -> UIView {
        let card = WildReportCardView(spacing: 0, inset: 0)
        card.layer.cornerRadius = 18
        let title = WildReportUI.label("处理信息", font: .systemFont(ofSize: 18, weight: .bold), color: AppColor.textPrimary)
        let titleWrap = UIView()
        titleWrap.addSubview(title)
        title.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 18, left: 18, bottom: 10, right: 18))
        }
        card.stack.addArrangedSubview(titleWrap)
        card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理人", value: info.handlerName))
        card.stack.addArrangedSubview(makeInsetDivider())
        let phoneRow = WildReportHandlerInfoRowView(title: "处理人电话", value: info.maskedHandlerPhone, trailingIconName: "phone.fill")
        phoneRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handlerPhoneTapped)))
        phoneRow.accessibilityTraits.insert(.button)
        card.stack.addArrangedSubview(phoneRow)
        card.stack.addArrangedSubview(makeInsetDivider())
        card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理时间", value: info.processingAt))
        card.stack.addArrangedSubview(makeInsetDivider())
        card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理完成时间", value: info.processedAt))
        card.stack.addArrangedSubview(makeInsetDivider())
        card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理意见", value: info.processOpinion, multiline: true))

        if !info.completionMedia.isEmpty {
            card.stack.addArrangedSubview(makeInsetDivider())
            card.stack.addArrangedSubview(makeCompletionMediaSection(info: info))
        }
        return card
    }

    private func makeSupplementCard() -> UIView {
        let card = WildReportCardView(spacing: 14, inset: 18)
        card.layer.cornerRadius = 18
        card.stack.addArrangedSubview(WildReportUI.label("补充证据记录", font: .systemFont(ofSize: 18, weight: .bold), color: AppColor.textPrimary))
        let items = viewModel.supplementaryEvidences
        items.enumerated().forEach { index, item in
            let row = WildReportSupplementEvidenceRowView(item: item, displayIndex: index)
            row.onPreview = { [weak self] in
                self?.presentSupplementMediaPreview(item)
            }
            card.stack.addArrangedSubview(row)
            if index < items.count - 1 {
                card.stack.addArrangedSubview(makeDivider())
            }
        }
        return card
    }

    private func makeTimelineCard() -> UIView {
        let card = WildReportCardView(spacing: 14, inset: 18)
        card.layer.cornerRadius = 18
        card.stack.addArrangedSubview(WildReportUI.label("处理进度", font: .systemFont(ofSize: 18, weight: .bold), color: AppColor.textPrimary))
        viewModel.progressSteps.enumerated().forEach { index, step in
            card.stack.addArrangedSubview(WildReportDetailTimelineRowView(step: step, isLast: index == viewModel.progressSteps.count - 1))
        }
        return card
    }

    private func makeReporterMaterialsSection() -> UIView {
        let section = UIStackView()
        section.axis = .vertical
        section.spacing = 12
        section.addArrangedSubview(makeCompactMediaSectionHeader(
            iconName: "photo.on.rectangle.angled",
            title: "举报人材料",
            summary: viewModel.reporterMediaSummary,
            action: #selector(previewTapped)
        ))

        let mediaScroll = UIScrollView()
        mediaScroll.showsHorizontalScrollIndicator = false
        let mediaStack = UIStackView()
        mediaStack.axis = .horizontal
        mediaStack.spacing = 10
        mediaScroll.addSubview(mediaStack)
        mediaStack.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.height.equalTo(mediaScroll.frameLayoutGuide)
        }
        viewModel.reporterMediaItems.forEach { item in
            let tile = WildReportDetailMediaThumbnailView(reporterItem: item)
            tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reporterMediaTapped(_:))))
            tile.snp.makeConstraints { make in
                make.size.equalTo(108)
            }
            mediaStack.addArrangedSubview(tile)
        }
        mediaScroll.snp.makeConstraints { make in
            make.height.equalTo(108)
        }
        section.addArrangedSubview(mediaScroll)
        return section
    }

    private func makeCompactMediaSectionHeader(
        iconName: String,
        title: String,
        summary: String,
        action: Selector
    ) -> UIView {
        let control = UIControl()
        control.addTarget(self, action: action, for: .touchUpInside)

        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .center
        row.spacing = 8

        let icon = UIImageView(image: UIImage(systemName: iconName))
        icon.tintColor = AppColor.primary
        icon.contentMode = .scaleAspectFit
        icon.snp.makeConstraints { make in
            make.size.equalTo(16)
        }

        let titleLabel = WildReportUI.label(title, font: .systemFont(ofSize: 15, weight: .bold), color: AppColor.textPrimary)
        let spacer = UIView()
        let summaryLabel = WildReportUI.label(summary, font: .systemFont(ofSize: 13, weight: .medium), color: AppColor.textSecondary)
        summaryLabel.setContentHuggingPriority(.required, for: .horizontal)
        summaryLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

        let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
        chevron.tintColor = UIColor(hex: 0xB6BECA)
        chevron.contentMode = .scaleAspectFit
        chevron.snp.makeConstraints { make in
            make.size.equalTo(14)
        }

        row.addArrangedSubview(icon)
        row.addArrangedSubview(titleLabel)
        row.addArrangedSubview(spacer)
        row.addArrangedSubview(summaryLabel)
        row.addArrangedSubview(chevron)
        control.addSubview(row)
        row.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        control.snp.makeConstraints { make in
            make.height.equalTo(22)
        }
        return control
    }

    private func makePaymentScreenshotSection() -> UIView {
        let section = UIStackView()
        section.axis = .vertical
        section.spacing = 12
        section.addArrangedSubview(makeCompactMediaSectionHeader(
            iconName: "creditcard.fill",
            title: "支付截图",
            summary: viewModel.paymentScreenshotSummary,
            action: #selector(paymentPreviewTapped)
        ))

        let mediaScroll = UIScrollView()
        mediaScroll.showsHorizontalScrollIndicator = false
        let mediaStack = UIStackView()
        mediaStack.axis = .horizontal
        mediaStack.spacing = 10
        mediaScroll.addSubview(mediaStack)
        mediaStack.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.height.equalTo(mediaScroll.frameLayoutGuide)
        }
        viewModel.paymentScreenshotItems.forEach { item in
            let tile = WildReportDetailMediaThumbnailView(reporterItem: item)
            tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(paymentMediaTapped(_:))))
            tile.snp.makeConstraints { make in
                make.size.equalTo(108)
            }
            mediaStack.addArrangedSubview(tile)
        }
        mediaScroll.snp.makeConstraints { make in
            make.height.equalTo(108)
        }
        section.addArrangedSubview(mediaScroll)
        return section
    }

    private func makeCompletionMediaSection(info: WildReportHandlerInfo) -> UIView {
        let section = UIStackView()
        section.axis = .vertical
        section.spacing = 12
        section.layoutMargins = UIEdgeInsets(top: 14, left: 18, bottom: 16, right: 18)
        section.isLayoutMarginsRelativeArrangement = true
        section.addArrangedSubview(makeCompactMediaSectionHeader(
            iconName: "photo.on.rectangle.angled",
            title: "处理凭证",
            summary: info.completionMediaSummary,
            action: #selector(completionPreviewTapped)
        ))

        let mediaScroll = UIScrollView()
        mediaScroll.showsHorizontalScrollIndicator = false
        let mediaStack = UIStackView()
        mediaStack.axis = .horizontal
        mediaStack.spacing = 10
        mediaScroll.addSubview(mediaStack)
        mediaStack.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.height.equalTo(mediaScroll.frameLayoutGuide)
        }
        info.completionMedia.forEach { item in
            let tile = WildReportDetailMediaThumbnailView(completionItem: item)
            tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(completionMediaTapped(_:))))
            tile.snp.makeConstraints { make in
                make.size.equalTo(108)
            }
            mediaStack.addArrangedSubview(tile)
        }
        mediaScroll.snp.makeConstraints { make in
            make.height.equalTo(108)
        }
        section.addArrangedSubview(mediaScroll)
        return section
    }

    private func makeLocationSection() -> UIView {
        let section = UIStackView()
        section.axis = .vertical
        section.spacing = 12
        section.addArrangedSubview(makeLocationHeader())
        let mapPreview = WildReportDetailMapPreviewView(
            scenicName: viewModel.scenicAreaName,
            detailAddress: viewModel.detailAddress,
            coordinate: viewModel.mapCoordinate
        )
        section.addArrangedSubview(mapPreview)
        mapPreview.snp.makeConstraints { make in
            make.height.equalTo(98)
        }
        return section
    }

    private func makeLocationHeader() -> UIView {
        let control = UIControl()
        control.addTarget(self, action: #selector(locationTapped), for: .touchUpInside)

        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .center
        row.spacing = 14

        let iconBox = UIView()
        iconBox.backgroundColor = AppColor.primaryLight
        iconBox.layer.cornerRadius = 12
        let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
        icon.tintColor = AppColor.primary
        icon.contentMode = .scaleAspectFit
        iconBox.addSubview(icon)
        iconBox.snp.makeConstraints { make in
            make.size.equalTo(50)
        }
        icon.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(22)
        }

        let titleLabel = WildReportUI.label("位置", font: .systemFont(ofSize: 20, weight: .bold), color: AppColor.textPrimary)
        let spacer = UIView()
        let valueLabel = WildReportUI.label(viewModel.detailAddress, font: .systemFont(ofSize: 18, weight: .medium), color: AppColor.textSecondary)
        valueLabel.lineBreakMode = .byTruncatingTail
        valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
        chevron.tintColor = UIColor(hex: 0xB6BECA)
        chevron.contentMode = .scaleAspectFit
        chevron.snp.makeConstraints { make in
            make.size.equalTo(16)
        }

        row.addArrangedSubview(iconBox)
        row.addArrangedSubview(titleLabel)
        row.addArrangedSubview(spacer)
        row.addArrangedSubview(valueLabel)
        row.addArrangedSubview(chevron)
        control.addSubview(row)
        row.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        control.snp.makeConstraints { make in
            make.height.equalTo(50)
        }
        return control
    }

    private func makeSectionHeader(
        iconName: String,
        title: String,
        subtitle: String,
        trailingText: String? = nil,
        chevron: Bool,
        action: Selector?
    ) -> UIView {
        let control = UIControl()
        if let action {
            control.addTarget(self, action: action, for: .touchUpInside)
        }

        let iconBox = UIView()
        iconBox.backgroundColor = AppColor.primaryLight
        iconBox.layer.cornerRadius = 14
        let icon = UIImageView(image: UIImage(systemName: iconName))
        icon.tintColor = AppColor.primary
        icon.contentMode = .scaleAspectFit
        iconBox.addSubview(icon)
        control.addSubview(iconBox)

        let textStack = UIStackView()
        textStack.axis = .vertical
        textStack.spacing = 4
        textStack.addArrangedSubview(WildReportUI.label(title, font: .systemFont(ofSize: 20, weight: .bold), color: AppColor.textPrimary))
        if !subtitle.isEmpty {
            let subtitleLabel = WildReportUI.label(subtitle, font: .systemFont(ofSize: 18, weight: .medium), color: AppColor.textSecondary)
            subtitleLabel.adjustsFontSizeToFitWidth = true
            subtitleLabel.minimumScaleFactor = 0.8
            textStack.addArrangedSubview(subtitleLabel)
        }
        control.addSubview(textStack)

        let trailingLabel = WildReportUI.label(trailingText ?? "", font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textSecondary)
        trailingLabel.textAlignment = .right
        trailingLabel.setContentHuggingPriority(.required, for: .horizontal)
        trailingLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
        trailingLabel.isHidden = trailingText?.isEmpty != false
        control.addSubview(trailingLabel)

        let chevronView = UIImageView(image: UIImage(systemName: "chevron.right"))
        chevronView.tintColor = AppColor.textTertiary
        chevronView.isHidden = !chevron
        control.addSubview(chevronView)

        iconBox.snp.makeConstraints { make in
            make.leading.top.bottom.equalToSuperview()
            make.size.equalTo(50)
        }
        icon.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(24)
        }
        chevronView.snp.makeConstraints { make in
            make.trailing.centerY.equalToSuperview()
            make.size.equalTo(18)
        }
        trailingLabel.snp.makeConstraints { make in
            make.centerY.equalToSuperview()
            make.trailing.equalTo(chevronView.snp.leading).offset(-8)
        }
        textStack.snp.makeConstraints { make in
            make.leading.equalTo(iconBox.snp.trailing).offset(12)
            make.centerY.equalToSuperview()
            if trailingText?.isEmpty == false {
                make.trailing.lessThanOrEqualTo(trailingLabel.snp.leading).offset(-10)
            } else {
                make.trailing.equalTo(chevronView.snp.leading).offset(-10)
            }
        }
        control.snp.makeConstraints { make in
            make.height.greaterThanOrEqualTo(50)
        }
        return control
    }

    private func makeScenicPill() -> UIView {
        let wrapper = UIView()
        let pill = UIStackView()
        pill.axis = .horizontal
        pill.alignment = .center
        pill.spacing = 4
        pill.layoutMargins = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 10)
        pill.isLayoutMarginsRelativeArrangement = true
        pill.backgroundColor = AppColor.primaryLight
        pill.layer.cornerRadius = 13
        pill.clipsToBounds = true
        pill.setContentHuggingPriority(.required, for: .horizontal)
        pill.setContentCompressionResistancePriority(.required, for: .horizontal)

        let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
        icon.tintColor = AppColor.primary
        icon.snp.makeConstraints { make in
            make.size.equalTo(14)
        }
        let label = WildReportUI.label(viewModel.scenicAreaName, font: .app(.captionMedium), color: AppColor.primary)
        pill.addArrangedSubview(icon)
        pill.addArrangedSubview(label)
        wrapper.addSubview(pill)
        pill.snp.makeConstraints { make in
            make.top.bottom.leading.equalToSuperview()
            make.trailing.lessThanOrEqualToSuperview()
        }
        return wrapper
    }

    private func makeBottomButton(title: String, iconName: String, isPrimary: Bool) -> UIButton {
        let button = UIButton(type: .system)
        button.backgroundColor = isPrimary ? AppColor.primary : .white
        button.layer.cornerRadius = 14
        button.layer.borderWidth = isPrimary ? 0 : 1.2
        button.layer.borderColor = AppColor.primary.cgColor
        button.tintColor = isPrimary ? .white : AppColor.primary
        button.setTitleColor(isPrimary ? .white : AppColor.primary, for: .normal)
        button.setTitle(title, for: .normal)
        button.setImage(UIImage(systemName: iconName), for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
        button.titleLabel?.adjustsFontSizeToFitWidth = true
        button.titleLabel?.minimumScaleFactor = 0.82
        button.setConfigurationContentInsets(
            NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
        )
        button.configuration?.imagePlacement = .leading
        button.configuration?.imagePadding = 4
        return button
    }

    private func makeDivider() -> UIView {
        let divider = UIView()
        divider.backgroundColor = AppColor.cardOutline
        divider.snp.makeConstraints { make in
            make.height.equalTo(1)
        }
        return divider
    }

    private func makeInsetDivider() -> UIView {
        let wrapper = UIView()
        let divider = UIView()
        divider.backgroundColor = AppColor.cardOutline
        wrapper.addSubview(divider)
        wrapper.snp.makeConstraints { make in
            make.height.equalTo(1)
        }
        divider.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(18)
            make.top.bottom.equalToSuperview()
        }
        return wrapper
    }

    @objc private func shareTapped() {
        showLoading()
        Task { [weak self] in
            guard let self else { return }
            let payload = await self.viewModel.shareReport(api: self.api)
            await MainActor.run {
                self.hideLoading()
                guard let payload else { return }
                WeChatShareService.shareMiniProgram(payload, previewImage: WildReportShareCover.image) { [weak self] result in
                    self?.showWildReportShareResult(result)
                }
            }
        }
    }
    @objc private func previewTapped() { presentReporterMediaPreview(selectedKey: nil) }
    @objc private func paymentPreviewTapped() { presentPaymentMediaPreview(selectedKey: nil) }
    @objc private func completionPreviewTapped() { presentCompletionMediaPreview(selectedKey: nil) }
    @objc private func locationTapped() { viewModel.showLocationPreview() }
    @objc private func handlerPhoneTapped() {
        guard let phone = viewModel.handlerInfo?.handlerPhone.trimmingCharacters(in: .whitespacesAndNewlines),
              !phone.isEmpty,
              let url = URL(string: "tel://\(phone)") else {
            showToast("暂无处理人电话")
            return
        }
        UIApplication.shared.open(url)
    }

    @objc private func reporterMediaTapped(_ recognizer: UITapGestureRecognizer) {
        guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
        presentReporterMediaPreview(selectedKey: tile.previewKey)
    }

    @objc private func paymentMediaTapped(_ recognizer: UITapGestureRecognizer) {
        guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
        presentPaymentMediaPreview(selectedKey: tile.previewKey)
    }

    @objc private func completionMediaTapped(_ recognizer: UITapGestureRecognizer) {
        guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
        presentCompletionMediaPreview(selectedKey: tile.previewKey)
    }

    private func presentReporterMediaPreview(selectedKey: String?) {
        presentMediaPreview(entries: previewEntries(for: viewModel.reporterMediaItems), selectedKey: selectedKey, emptyMessage: "当前举报材料暂无可预览文件")
    }

    private func presentPaymentMediaPreview(selectedKey: String?) {
        presentMediaPreview(entries: previewEntries(for: viewModel.paymentScreenshotItems), selectedKey: selectedKey, emptyMessage: "当前支付截图暂无可预览文件")
    }

    private func presentCompletionMediaPreview(selectedKey: String?) {
        let entries = viewModel.handlerInfo?.completionMedia.compactMap { item -> (key: String, item: MediaPreviewItem)? in
            guard let key = item.previewKey,
                  let previewItem = MediaPreviewItem(wildReportCompletionItem: item)
            else { return nil }
            return (key, previewItem)
        } ?? []
        presentMediaPreview(entries: entries, selectedKey: selectedKey, emptyMessage: "当前处理凭证暂无可预览文件")
    }

    private func presentSupplementMediaPreview(_ record: WildReportSupplementaryEvidence) {
        let items = record.evidences.compactMap { evidence -> MediaPreviewItem? in
            if evidence.isVideo {
                return .remoteVideo(evidence.fileURL)
            }
            if evidence.isImage {
                return .remoteImage(evidence.fileURL)
            }
            return nil
        }
        guard !items.isEmpty else {
            showToast("当前补充记录暂无可预览文件")
            return
        }
        presentMediaPreview(items: items, startIndex: 0)
    }

    private func previewEntries(for mediaItems: [WildReportReporterMediaItem]) -> [(key: String, item: MediaPreviewItem)] {
        mediaItems.compactMap { mediaItem in
            guard let key = mediaItem.previewKey,
                  let previewItem = MediaPreviewItem(wildReportReporterItem: mediaItem)
            else { return nil }
            return (key, previewItem)
        }
    }

    private func presentMediaPreview(
        entries: [(key: String, item: MediaPreviewItem)],
        selectedKey: String?,
        emptyMessage: String
    ) {
        guard !entries.isEmpty else {
            showToast(emptyMessage)
            return
        }
        let startIndex = selectedKey.flatMap { key in entries.firstIndex { $0.key == key } } ?? 0
        presentMediaPreview(items: entries.map(\.item), startIndex: startIndex)
    }

    @objc private func supplementTapped() {
        navigationController?.pushViewController(
            WildReportSupplementEvidenceViewController(
                record: viewModel.record,
                homeViewModel: homeViewModel,
                api: api,
                onSupplementSuccess: { [weak self] in
                    guard let self else { return }
                    Task {
                        await self.viewModel.loadDetailIfNeeded(api: self.api, force: true)
                    }
                }
            ),
            animated: true
        )
    }

    @objc private func mapTapped() {
        navigationController?.pushViewController(
            WildReportRiskMapViewController(),
            animated: true
        )
    }
}

/// 举报详情页图文竖排按钮，用于 Summary 卡片中的分享入口。
private final class WildReportDetailIconTextButton: UIControl {
    private let iconView = UIImageView()
    private let titleLabel = UILabel()

    init(title: String, iconName: String) {
        super.init(frame: .zero)
        backgroundColor = AppColor.primaryLight
        layer.cornerRadius = 14

        iconView.image = UIImage(systemName: iconName)
        iconView.tintColor = AppColor.primary
        iconView.contentMode = .scaleAspectFit
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 10, weight: .bold)
        titleLabel.textColor = AppColor.primary
        titleLabel.textAlignment = .center

        addSubview(iconView)
        addSubview(titleLabel)
        iconView.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(8)
            make.centerX.equalToSuperview()
            make.size.equalTo(18)
        }
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(iconView.snp.bottom).offset(2)
            make.leading.trailing.equalToSuperview().inset(4)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 举报详情页补充证据记录行，包含媒体时提供整行预览交互。
private final class WildReportSupplementEvidenceRowView: UIControl {
    var onPreview: (() -> Void)?

    private let previewEnabled: Bool

    init(item: WildReportSupplementaryEvidence, displayIndex: Int) {
        previewEnabled = item.hasMedia
        super.init(frame: .zero)

        let sequenceTitle = WildReportSupplementDisplayFormatter.sequenceTitle(forDisplayIndex: displayIndex)
        let header = UIStackView()
        header.axis = .horizontal
        header.alignment = .center
        header.spacing = 6
        header.isUserInteractionEnabled = false
        header.addArrangedSubview(WildReportUI.label(item.submitTime, font: .app(.captionMedium), color: AppColor.textSecondary))
        header.addArrangedSubview(UIView())
        header.addArrangedSubview(WildReportUI.label(sequenceTitle, font: .app(.captionMedium), color: AppColor.primary))

        if previewEnabled {
            let chevron = UIImageView(image: UIImage(systemName: "chevron.right"))
            chevron.tintColor = AppColor.primary
            chevron.contentMode = .scaleAspectFit
            chevron.snp.makeConstraints { make in
                make.size.equalTo(12)
            }
            header.addArrangedSubview(chevron)
        }

        let summaryLabel = WildReportUI.label(item.summaryText, font: .app(.bodyMedium), color: AppColor.textPrimary, lines: 0)
        summaryLabel.isUserInteractionEnabled = false
        let contentStack = UIStackView(arrangedSubviews: [header, summaryLabel])
        contentStack.axis = .vertical
        contentStack.spacing = 8
        contentStack.isUserInteractionEnabled = false

        addSubview(contentStack)
        contentStack.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        isEnabled = previewEnabled
        guard previewEnabled else { return }

        addTarget(self, action: #selector(previewTapped), for: .touchUpInside)
        isAccessibilityElement = true
        accessibilityTraits = .button
        accessibilityLabel = [sequenceTitle, item.submitTime, item.summaryText]
            .filter { !$0.isEmpty }
            .joined(separator: "，")
        accessibilityHint = "双击全屏预览图片或视频"
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isHighlighted: Bool {
        didSet {
            guard previewEnabled else { return }
            UIView.animate(withDuration: 0.15) {
                self.alpha = self.isHighlighted ? 0.55 : 1
            }
        }
    }

    @objc private func previewTapped() {
        onPreview?()
    }
}

/// 举报详情页信息行，适配短文本与多行说明。
private final class WildReportDetailInfoRowView: UIView {
    init(title: String, value: String, multiline: Bool = false, trailingIconName: String? = nil) {
        super.init(frame: .zero)

        let titleLabel = WildReportUI.label(title, font: .app(.captionMedium), color: AppColor.textSecondary)
        let valueLabel = WildReportUI.label(value, font: .app(.subtitle), color: AppColor.textPrimary, lines: multiline ? 0 : 1)
        valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        addSubview(titleLabel)
        addSubview(valueLabel)

        if let trailingIconName {
            let icon = UIImageView(image: UIImage(systemName: trailingIconName))
            icon.tintColor = AppColor.primary
            icon.contentMode = .scaleAspectFit
            addSubview(icon)
            icon.snp.makeConstraints { make in
                make.trailing.equalToSuperview().inset(18)
                make.centerY.equalTo(valueLabel)
                make.size.equalTo(16)
            }
            valueLabel.snp.makeConstraints { make in
                make.trailing.equalTo(icon.snp.leading).offset(-6)
            }
        }

        titleLabel.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview().inset(UIEdgeInsets(top: 14, left: 18, bottom: 0, right: 18))
        }
        valueLabel.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(7)
            make.leading.equalToSuperview().offset(18)
            if trailingIconName == nil {
                make.trailing.equalToSuperview().inset(18)
            }
            make.bottom.equalToSuperview().inset(14)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 举报详情页处理信息单行，左侧字段名、右侧内容。
private final class WildReportHandlerInfoRowView: UIView {
    init(title: String, value: String, trailingIconName: String? = nil) {
        super.init(frame: .zero)

        let titleLabel = WildReportUI.label(title, font: .app(.captionMedium), color: AppColor.textSecondary)
        titleLabel.setContentHuggingPriority(.required, for: .horizontal)
        titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

        let valueLabel = WildReportUI.label(value, font: .app(.subtitle), color: AppColor.textPrimary, lines: 1)
        valueLabel.textAlignment = .right
        valueLabel.lineBreakMode = .byTruncatingTail
        valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        addSubview(titleLabel)
        addSubview(valueLabel)

        if let trailingIconName {
            let icon = UIImageView(image: UIImage(systemName: trailingIconName))
            icon.tintColor = AppColor.primary
            icon.contentMode = .scaleAspectFit
            addSubview(icon)
            icon.snp.makeConstraints { make in
                make.trailing.equalToSuperview().inset(18)
                make.centerY.equalToSuperview()
                make.size.equalTo(16)
            }
            valueLabel.snp.makeConstraints { make in
                make.trailing.equalTo(icon.snp.leading).offset(-6)
            }
        }

        titleLabel.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(18)
            make.centerY.equalToSuperview()
        }
        valueLabel.snp.makeConstraints { make in
            make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(14)
            make.centerY.equalToSuperview()
            if trailingIconName == nil {
                make.trailing.equalToSuperview().inset(18)
            }
        }
        snp.makeConstraints { make in
            make.height.equalTo(48)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

private extension MediaPreviewItem {
    init?(wildReportReporterItem item: WildReportReporterMediaItem) {
        guard let urlText = item.previewKey else { return nil }
        switch item {
        case .image:
            guard let previewItem = MediaPreviewItem.remoteImage(urlText) else { return nil }
            self = previewItem
        case .video:
            guard let previewItem = MediaPreviewItem.remoteVideo(urlText) else { return nil }
            self = previewItem
        }
    }

    init?(wildReportCompletionItem item: WildReportCompletionMediaItem) {
        guard let urlText = item.previewKey else { return nil }
        switch item.kind {
        case .image:
            guard let previewItem = MediaPreviewItem.remoteImage(urlText) else { return nil }
            self = previewItem
        case .video:
            guard let previewItem = MediaPreviewItem.remoteVideo(urlText) else { return nil }
            self = previewItem
        }
    }
}

private extension BaseViewController {
    @MainActor
    func showWildReportShareResult(_ result: WeChatShareResult) {
        switch result {
        case .success:
            showToast("分享成功")
        case .cancelled:
            showToast("已取消分享")
        case .notInstalled:
            showToast("请先安装微信")
        case .unsupported:
            showToast("当前微信版本不支持分享")
        case .invalidPayload(let message):
            showToast(message)
        case .sendFailed:
            showToast("微信分享发送失败")
        case .unknown:
            showToast("微信分享失败")
        }
    }
}

private extension WildReportReporterMediaItem {
    var previewKey: String? {
        guard let text = fileURL?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { return nil }
        return text
    }
}

private extension WildReportCompletionMediaItem {
    var previewKey: String? {
        let text = coverURL.trimmingCharacters(in: .whitespacesAndNewlines)
        return text.isEmpty ? nil : text
    }
}

/// 举报详情页材料缩略图，展示远程图片、视频和处理凭证。
private final class WildReportDetailMediaThumbnailView: UIView {
    let previewKindText: String
    let previewKey: String?
    private let gradient = CAGradientLayer()
    private let remoteImageView = UIImageView()
    private let footerLabel = PaddingLabel(insets: UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8))
    private let playView = UIImageView(image: UIImage(systemName: "play.circle.fill"))

    init(reporterItem: WildReportReporterMediaItem) {
        previewKey = reporterItem.previewKey
        switch reporterItem {
        case .image(let index, let url, let fileName, let fileTypeText):
            previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)张举报图片"
            super.init(frame: .zero)
            configure(kind: .image(index: index), footerText: fileTypeText ?? "图片 \(index + 1)", imageURL: url, showsFooter: false)
        case .video(let index, _, let fileName, let fileTypeText):
            previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)段举报视频"
            super.init(frame: .zero)
            configure(kind: .video(index: index, duration: fileTypeText ?? "00:32"), footerText: fileTypeText ?? "视频 \(index + 1)", showsFooter: false)
        }
    }

    init(completionItem: WildReportCompletionMediaItem) {
        previewKey = completionItem.previewKey
        switch completionItem.kind {
        case .image:
            previewKindText = "处理凭证图片"
            super.init(frame: .zero)
            configure(kind: .image(index: 0), footerText: "凭证", imageURL: completionItem.coverURL)
        case .video:
            previewKindText = "处理凭证视频"
            super.init(frame: .zero)
            configure(kind: .video(index: 0, duration: completionItem.duration ?? "00:32"), footerText: completionItem.duration ?? "视频")
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

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

    private func configure(kind: Kind, footerText: String, imageURL: String? = nil, showsFooter: Bool = true) {
        clipsToBounds = true
        layer.cornerRadius = 10
        gradient.colors = kind.gradientColors
        gradient.startPoint = CGPoint(x: 0, y: 0)
        gradient.endPoint = CGPoint(x: 1, y: 1)
        layer.insertSublayer(gradient, at: 0)

        if let imageURL, let url = URL(string: imageURL) {
            remoteImageView.contentMode = .scaleAspectFill
            remoteImageView.clipsToBounds = true
            remoteImageView.kf.setImage(with: url, placeholder: UIImage(systemName: "photo"))
            addSubview(remoteImageView)
            remoteImageView.snp.makeConstraints { make in
                make.edges.equalToSuperview()
            }
        }

        if imageURL == nil {
            addPlaceholderArtwork(for: kind)
        }

        footerLabel.text = footerText
        footerLabel.font = .systemFont(ofSize: 11, weight: .bold)
        footerLabel.textColor = .white
        footerLabel.backgroundColor = UIColor.black.withAlphaComponent(0.24)
        footerLabel.layer.cornerRadius = 9
        footerLabel.clipsToBounds = true
        footerLabel.isHidden = !showsFooter
        addSubview(footerLabel)

        footerLabel.snp.makeConstraints { make in
            make.leading.bottom.equalToSuperview().inset(8)
        }

        if case .video(_, let duration) = kind {
            let playCircle = UIView()
            playCircle.backgroundColor = UIColor.black.withAlphaComponent(0.08)
            playCircle.layer.cornerRadius = 21
            playCircle.layer.borderWidth = 2.5
            playCircle.layer.borderColor = UIColor.white.cgColor
            addSubview(playCircle)
            playView.image = UIImage(systemName: "play.fill")
            playView.tintColor = .white
            addSubview(playView)
            let durationLabel = PaddingLabel(insets: UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6))
            durationLabel.text = duration
            durationLabel.font = .systemFont(ofSize: 14, weight: .semibold)
            durationLabel.textColor = .white
            durationLabel.backgroundColor = .clear
            durationLabel.layer.shadowColor = UIColor.black.cgColor
            durationLabel.layer.shadowOpacity = 0.25
            durationLabel.layer.shadowRadius = 4
            durationLabel.layer.shadowOffset = CGSize(width: 0, height: 2)
            addSubview(durationLabel)
            playCircle.snp.makeConstraints { make in
                make.center.equalToSuperview()
                make.size.equalTo(42)
            }
            playView.snp.makeConstraints { make in
                make.centerX.equalToSuperview().offset(2)
                make.centerY.equalToSuperview()
                make.size.equalTo(18)
            }
            durationLabel.snp.makeConstraints { make in
                make.trailing.bottom.equalToSuperview().inset(10)
            }
        }
    }

    private func addPlaceholderArtwork(for kind: Kind) {
        let index = min(kind.artworkIndex, 2)
        let mountain = UIImageView(image: UIImage(systemName: "mountain.2.fill"))
        mountain.tintColor = UIColor.white.withAlphaComponent(0.32)
        mountain.contentMode = .scaleAspectFit
        addSubview(mountain)

        let camera = UIImageView(image: UIImage(systemName: "camera.fill"))
        camera.tintColor = UIColor.white.withAlphaComponent(0.90)
        camera.contentMode = .scaleAspectFit
        addSubview(camera)

        let person = UIImageView(image: UIImage(systemName: "person.fill"))
        person.tintColor = UIColor(hex: 0x101827).withAlphaComponent(0.78)
        person.contentMode = .scaleAspectFit
        addSubview(person)

        mountain.snp.makeConstraints { make in
            make.width.height.equalTo(52)
            make.centerX.equalToSuperview().offset(-16)
            make.centerY.equalToSuperview().offset(-8)
        }
        camera.snp.makeConstraints { make in
            make.width.height.equalTo(22)
            make.trailing.equalToSuperview().inset(10)
            make.centerY.equalToSuperview().offset(-18)
        }
        person.snp.makeConstraints { make in
            make.width.height.equalTo(44)
            make.trailing.equalToSuperview().inset(24 - index * 12)
            make.bottom.equalToSuperview().offset(10)
        }
    }

    /// 举报详情页缩略图类型。
    private enum Kind {
        case image(index: Int)
        case video(index: Int, duration: String)

        var artworkIndex: Int {
            switch self {
            case .image(let index), .video(let index, _):
                return index
            }
        }

        var gradientColors: [CGColor] {
            switch self {
            case .image(let index):
                switch index {
                case 1:
                    return [UIColor(hex: 0xD7E8F8).cgColor, UIColor(hex: 0x7E9CB4).cgColor]
                case 2:
                    return [UIColor(hex: 0xBFD9EE).cgColor, UIColor(hex: 0x435B70).cgColor]
                default:
                    return [UIColor(hex: 0xC7DFEC).cgColor, UIColor(hex: 0x2D3D4B).cgColor]
                }
            case .video:
                return [UIColor(hex: 0xBFD9EE).cgColor, UIColor(hex: 0x435B70).cgColor]
            }
        }
    }
}

/// 举报详情页地图预览，绘制简化道路、路径和定位点。
#if targetEnvironment(simulator)
private final class WildReportDetailMapPreviewView: UIView {
    private let mapView: MKMapView

    init(scenicName: String, detailAddress: String, coordinate: CLLocationCoordinate2D?) {
        mapView = MKMapView(frame: .zero)
        super.init(frame: .zero)
        backgroundColor = AppColor.pageBackgroundSoft
        layer.cornerRadius = 10
        clipsToBounds = true

        mapView.showsCompass = false
        mapView.showsScale = false
        mapView.isScrollEnabled = false
        mapView.isZoomEnabled = false
        mapView.isRotateEnabled = false
        mapView.isPitchEnabled = false
        addSubview(mapView)
        mapView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        guard let coordinate else { return }
        let annotation = MKPointAnnotation()
        annotation.coordinate = coordinate
        annotation.title = scenicName
        annotation.subtitle = detailAddress
        mapView.addAnnotation(annotation)
        mapView.setRegion(
            MKCoordinateRegion(
                center: coordinate,
                span: MKCoordinateSpan(latitudeDelta: 0.006, longitudeDelta: 0.006)
            ),
            animated: false
        )
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
#else
private final class WildReportDetailMapPreviewView: UIView {
    private let mapView: MAMapView

    init(scenicName: String, detailAddress: String, coordinate: CLLocationCoordinate2D?) {
        _ = AMapBootstrap.configureIfNeeded()
        mapView = MAMapView(frame: .zero)
        super.init(frame: .zero)
        backgroundColor = AppColor.pageBackgroundSoft
        layer.cornerRadius = 10
        clipsToBounds = true

        mapView.showsCompass = false
        mapView.showsScale = false
        mapView.isScrollEnabled = false
        mapView.isZoomEnabled = false
        mapView.isRotateEnabled = false
        mapView.isRotateCameraEnabled = false
        mapView.zoomLevel = 16
        addSubview(mapView)
        mapView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        guard let coordinate else { return }
        let annotation = MAPointAnnotation()
        annotation.coordinate = coordinate
        annotation.title = scenicName
        annotation.subtitle = detailAddress
        mapView.addAnnotation(annotation)
        mapView.setCenter(coordinate, animated: false)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
#endif

/// 举报详情页处理进度时间线单行。
private final class WildReportDetailTimelineRowView: UIView {
    init(step: WildReportProgressStep, isLast: Bool) {
        super.init(frame: .zero)

        let leftColumn = UIView()
        let node = WildReportDetailTimelineNodeView(state: step.state)
        let line = UIView()
        line.backgroundColor = step.state == .pending ? AppColor.cardOutline : AppColor.primary.withAlphaComponent(0.45)
        line.isHidden = isLast
        leftColumn.addSubview(line)
        leftColumn.addSubview(node)
        addSubview(leftColumn)

        let textStack = UIStackView()
        textStack.axis = .vertical
        textStack.spacing = 5
        let isPending = step.state == .pending
        let title = WildReportUI.label(
            step.title,
            font: .systemFont(ofSize: 16, weight: step.state == .current ? .bold : .semibold),
            color: isPending ? AppColor.textTertiary : AppColor.textPrimary
        )
        textStack.addArrangedSubview(title)
        if !isPending || !step.timeText.isEmpty {
            textStack.addArrangedSubview(WildReportUI.label(
                step.timeText,
                font: .app(.captionMedium),
                color: step.state == .current ? AppColor.primary : AppColor.textSecondary,
                lines: 0
            ))
        }
        addSubview(textStack)

        leftColumn.snp.makeConstraints { make in
            make.leading.top.bottom.equalToSuperview()
            make.width.equalTo(24)
        }
        node.snp.makeConstraints { make in
            make.top.centerX.equalToSuperview()
            make.size.equalTo(24)
        }
        line.snp.makeConstraints { make in
            make.top.equalTo(node.snp.bottom).offset(4)
            make.centerX.equalTo(node)
            make.bottom.equalToSuperview().inset(-8)
            make.width.equalTo(2)
        }
        textStack.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(1)
            make.leading.equalTo(leftColumn.snp.trailing).offset(12)
            make.trailing.equalToSuperview()
            make.bottom.equalToSuperview().inset(16)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 举报详情页时间线节点，区分完成、当前和待处理状态。
private final class WildReportDetailTimelineNodeView: UIView {
    private let state: WildReportProgressStepState

    init(state: WildReportProgressStepState) {
        self.state = state
        super.init(frame: .zero)
        backgroundColor = .clear
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        let circle = UIBezierPath(ovalIn: rect.insetBy(dx: 1.5, dy: 1.5))
        switch state {
        case .completed:
            AppColor.primary.setFill()
            circle.fill()
            let check = UIBezierPath()
            check.move(to: CGPoint(x: rect.width * 0.30, y: rect.height * 0.52))
            check.addLine(to: CGPoint(x: rect.width * 0.45, y: rect.height * 0.66))
            check.addLine(to: CGPoint(x: rect.width * 0.72, y: rect.height * 0.36))
            UIColor.white.setStroke()
            check.lineWidth = 2.2
            check.lineCapStyle = .round
            check.lineJoinStyle = .round
            check.stroke()
        case .current:
            AppColor.primary.setStroke()
            circle.lineWidth = 2
            circle.stroke()
            AppColor.primary.setFill()
            UIBezierPath(ovalIn: rect.insetBy(dx: 7, dy: 7)).fill()
        case .pending:
            AppColor.cardOutline.setStroke()
            circle.lineWidth = 2
            circle.stroke()
        }
    }
}
