//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

class CVComponentCollapseSet: CVComponentBase, CVRootComponent {

    var componentKey: CVComponentKey { .collapseSet }

    var cellReuseIdentifier: CVCellReuseIdentifier { .collapseSet }

    let isDedicatedCell = true

    private let collapseSet: CVComponentState.CollapseSet

    init(itemModel: CVItemModel, collapseSet: CVComponentState.CollapseSet) {
        self.collapseSet = collapseSet
        super.init(itemModel: itemModel)
    }

    // MARK: - CVRootComponent

    func configureCellRootComponent(
        cellView: UIView,
        cellMeasurement: CVCellMeasurement,
        componentDelegate: CVComponentDelegate,
        messageSwipeActionState: CVMessageSwipeActionState,
        componentView: CVComponentView,
    ) {
        Self.configureCellRootComponent(
            rootComponent: self,
            cellView: cellView,
            cellMeasurement: cellMeasurement,
            componentDelegate: componentDelegate,
            componentView: componentView,
        )
    }

    func buildComponentView(componentDelegate: CVComponentDelegate) -> CVComponentView {
        CVComponentViewCollapseSet()
    }

    override func wallpaperBlurView(componentView: CVComponentView) -> CVWallpaperBlurView? {
        guard let componentView = componentView as? CVComponentViewCollapseSet else {
            owsFailDebug("Unexpected componentView.")
            return nil
        }
        return componentView.wallpaperBlurView
    }

    func configureForRendering(
        componentView: CVComponentView,
        cellMeasurement: CVCellMeasurement,
        componentDelegate: CVComponentDelegate,
    ) {
        guard let componentView = componentView as? CVComponentViewCollapseSet else {
            owsFailDebug("Unexpected componentView.")
            componentView.reset()
            return
        }

        let hasWallpaper = conversationStyle.hasWallpaper
        let wallpaperModeHasChanged = hasWallpaper != componentView.hasWallpaper
        let isReusing = componentView.rootView.superview != nil
            && componentView.label.superview != nil
            && !wallpaperModeHasChanged

        if !isReusing {
            componentView.reset()
        }

        componentView.hasWallpaper = hasWallpaper

        labelConfig.applyForRendering(label: componentView.label)

        if isReusing {
            componentView.outerStack.configureForReuse(
                config: outerStackConfig,
                cellMeasurement: cellMeasurement,
                measurementKey: Self.measurementKey_outerStack,
            )
        } else {
            componentView.outerStack.configure(
                config: outerStackConfig,
                cellMeasurement: cellMeasurement,
                measurementKey: Self.measurementKey_outerStack,
                subviews: [componentView.label],
            )

            let bubbleView: UIView
            if hasWallpaper {
                let wallpaperBlurView = componentView.ensureWallpaperBlurView()
                configureWallpaperBlurView(
                    wallpaperBlurView: wallpaperBlurView,
                    componentDelegate: componentDelegate,
                    bubbleConfig: BubbleConfiguration(
                        corners: .capsule(maxRadius: 16),
                        stroke: ConversationStyle.bubbleStroke(isDarkThemeEnabled: isDarkThemeEnabled),
                    ),
                )
                bubbleView = wallpaperBlurView
            } else {
                let solidBackgroundView = componentView.solidBackgroundView
                solidBackgroundView.layer.cornerRadius = 16
                solidBackgroundView.backgroundColor = .Signal.secondaryFill
                bubbleView = solidBackgroundView
            }
            componentView.outerStack.addSubview(bubbleView)
            componentView.outerStack.sendSubviewToBack(bubbleView)
            // This seemed easier than adding an entirely new ManualStackView
            // just to constrain the label and background to
            componentView.outerStack.addLayoutBlock { [label = componentView.label] _ in
                bubbleView.frame = label.frame.inset(by: Self.backgroundLayoutInsets)
            }
        }

        if
            hasWallpaper,
            let wallpaperBlurView = componentView.wallpaperBlurView
        {
            wallpaperBlurView.applyLayout()
            wallpaperBlurView.updateIfNecessary()
        }

        componentView.outerStack.isAccessibilityElement = true
        componentView.outerStack.accessibilityLabel = titleString
        componentView.outerStack.accessibilityTraits = .button
        componentView.outerStack.accessibilityHint = collapseSet.isExpanded
            ? OWSLocalizedString(
                "COLLAPSE_SET_ACCESSIBILITY_HINT_COLLAPSE",
                comment: "VoiceOver hint for an expanded collapse set button.",
            )
            : OWSLocalizedString(
                "COLLAPSE_SET_ACCESSIBILITY_HINT_EXPAND",
                comment: "VoiceOver hint for a collapsed collapse set button.",
            )
    }

    // MARK: - Events

    override func handleTap(
        sender: UIGestureRecognizer,
        componentDelegate: CVComponentDelegate,
        componentView: CVComponentView,
        renderItem: CVRenderItem,
    ) -> Bool {
        componentDelegate.didTapCollapseSet(collapseSetId: interaction.uniqueId)
        return true
    }

    // MARK: - Measurement

    fileprivate static let measurementKey_outerStack = "CVComponentCollapseSet.outerStack"

    func measure(maxWidth: CGFloat, measurementBuilder: CVCellMeasurement.Builder) -> CGSize {
        owsAssertDebug(maxWidth > 0)
        let availableWidth = max(
            0,
            maxWidth - outerStackConfig.layoutMargins.totalWidth,
        )
        let labelSize = CVText.measureLabel(config: labelConfig, maxWidth: availableWidth)
        let outerMeasurement = ManualStackView.measure(
            config: outerStackConfig,
            measurementBuilder: measurementBuilder,
            measurementKey: Self.measurementKey_outerStack,
            subviewInfos: [labelSize.asManualSubviewInfo(hasFixedWidth: true)],
        )
        return outerMeasurement.measuredSize
    }

    // MARK: - Layout

    private var outerStackConfig: CVStackViewConfig {
        CVStackViewConfig(
            axis: .vertical,
            alignment: .center,
            spacing: 0,
            layoutMargins: UIEdgeInsets(
                top: 4 + Self.labelContentInsets.top,
                leading: conversationStyle.fullWidthGutterLeading + Self.labelContentInsets.leading,
                bottom: 4 + Self.labelContentInsets.bottom,
                trailing: conversationStyle.fullWidthGutterTrailing + Self.labelContentInsets.trailing,
            ),
        )
    }

    private static var backgroundLayoutInsets: UIEdgeInsets {
        UIEdgeInsets(
            top: -labelContentInsets.top,
            leading: -labelContentInsets.leading,
            bottom: -labelContentInsets.bottom,
            trailing: -labelContentInsets.trailing,
        )
    }

    // MARK: - Content

    private var labelFont: UIFont { .dynamicTypeFootnote.medium() }

    private static var labelContentInsets: NSDirectionalEdgeInsets {
        NSDirectionalEdgeInsets(hMargin: 14, vMargin: 5)
    }

    private var leadingIcon: SignalSymbol {
        switch collapseSet.collapseSetType {
        case .groupUpdates: return .group
        case .chatUpdates: return .thread
        case .callEvents: return .phone
        case .timerChanges: return .timer
        }
    }

    private var labelConfig: CVLabelConfig {
        CVLabelConfig(
            text: .attributedText(titleAttributedString),
            displayConfig: .forUnstyledText(font: labelFont, textColor: .Signal.label),
            font: labelFont,
            textColor: .Signal.label,
            numberOfLines: 0,
            lineBreakMode: .byWordWrapping,
            textAlignment: .center,
        )
    }

    private var titleAttributedString: NSAttributedString {
        let labelText = summaryLabel(
            count: collapseSet.collapsedInteractions.count,
            type: collapseSet.collapseSetType,
            finalTimerDescription: collapseSet.finalTimerDescription,
        )

        let nbsp = SignalSymbol.LeadingCharacter.nonBreakingSpace.rawValue
        let chevron: SignalSymbol = collapseSet.isExpanded ? .chevronUp : .chevronDown

        let result = NSMutableAttributedString()
        result.append(leadingIcon.attributedString(
            for: .footnote,
            clamped: false,
            attributes: [.foregroundColor: UIColor.Signal.label],
        ))
        result.append(NSAttributedString(
            string: "\(nbsp)\(labelText)\(nbsp)",
            attributes: [
                .font: labelFont,
                .foregroundColor: UIColor.Signal.label,
            ],
        ))
        result.append(chevron.attributedString(
            for: .footnote,
            clamped: false,
            attributes: [.foregroundColor: UIColor.Signal.label],
        ))
        return result
    }

    private var titleString: String {
        summaryLabel(
            count: collapseSet.collapsedInteractions.count,
            type: collapseSet.collapseSetType,
            finalTimerDescription: collapseSet.finalTimerDescription,
        )
    }

    private func summaryLabel(
        count: Int,
        type: CollapseSetInteraction.MessagesType,
        finalTimerDescription: String? = nil,
    ) -> String {
        switch type {
        case .groupUpdates:
            return String(
                format: OWSLocalizedString(
                    "COLLAPSE_SET_GROUP_UPDATES_%d",
                    tableName: "PluralAware",
                    comment: "Label for a collapsed group of group update events. Embeds {{number of events}}.",
                ),
                count,
            )
        case .chatUpdates:
            return String(
                format: OWSLocalizedString(
                    "COLLAPSE_SET_CHAT_UPDATES_%d",
                    tableName: "PluralAware",
                    comment: "Label for a collapsed group of chat update events. Embeds {{number of events}}.",
                ),
                count,
            )
        case .timerChanges:
            let finalTimer: String
            if let finalTimerDescription {
                finalTimer = finalTimerDescription
            } else {
                owsFailDebug("disappearing message timer collapse set does not have final timer description")
                finalTimer = ""
            }
            return String(
                format: OWSLocalizedString(
                    "COLLAPSE_SET_TIMER_CHANGES_WITH_FINAL_TIMER_%d",
                    tableName: "PluralAware",
                    comment: "Label for collapsed disappearing message timer changes showing the final timer value. Embeds {{number of events}} and {{timer description}}.",
                ),
                count,
                finalTimer,
            )
        case .callEvents:
            return String(
                format: OWSLocalizedString(
                    "COLLAPSE_SET_CALL_EVENTS_%d",
                    tableName: "PluralAware",
                    comment: "Label for a collapsed group of call events. Embeds {{number of events}}.",
                ),
                count,
            )
        }
    }

    // MARK: - CVComponentViewCollapseSet

    class CVComponentViewCollapseSet: NSObject, CVComponentView {

        fileprivate let outerStack = ManualStackView(name: "collapseSet.outerStack")
        fileprivate let label = CVLabel()
        fileprivate let solidBackgroundView = UIView()

        fileprivate var wallpaperBlurView: CVWallpaperBlurView?
        fileprivate func ensureWallpaperBlurView() -> CVWallpaperBlurView {
            if let wallpaperBlurView = self.wallpaperBlurView {
                return wallpaperBlurView
            }
            let wallpaperBlurView = CVWallpaperBlurView()
            self.wallpaperBlurView = wallpaperBlurView
            return wallpaperBlurView
        }

        fileprivate var hasWallpaper = false

        var isDedicatedCellView = false

        var rootView: UIView { outerStack }

        func setIsCellVisible(_ isCellVisible: Bool) {}

        func reset() {
            label.reset()
            solidBackgroundView.backgroundColor = nil
            wallpaperBlurView?.removeFromSuperview()
            wallpaperBlurView = nil
            hasWallpaper = false
            outerStack.reset()
        }
    }
}
