import UIKit

/// Pure UIKit message cell for text messages (with optional reply quote).
/// Replaces UIHostingConfiguration + SwiftUI for the most common message type.
/// Features: Figma-accurate bubble tail, context menu, swipe-to-reply, reply quote.
final class NativeTextBubbleCell: UICollectionViewCell {

    // MARK: - Constants

    private static let mainRadius: CGFloat = 16
    private static let smallRadius: CGFloat = 5
    private static let tailProtrusion: CGFloat = 6
    private static let textFont = UIFont.systemFont(ofSize: 17, weight: .regular)
    private static let timestampFont = UIFont.systemFont(ofSize: 9, weight: .regular)
    private static let replyNameFont = UIFont.systemFont(ofSize: 13, weight: .semibold)
    private static let replyTextFont = UIFont.systemFont(ofSize: 13, weight: .regular)
    private static let outgoingColor = UIColor(red: 0.2, green: 0.565, blue: 0.925, alpha: 1) // #3390EC
    private static let incomingColor = UIColor(red: 0.17, green: 0.17, blue: 0.18, alpha: 1) // #2B2B2E
    private static let replyQuoteHeight: CGFloat = 41

    // Cached checkmark images (Telegram-exact shapes)
    private static let singleCheckImage: UIImage = StatusIconRenderer.renderShape(SingleCheckmarkShape(), size: CGSize(width: 12, height: 8.8))
    private static let doubleCheckImage: UIImage = StatusIconRenderer.renderShape(DoubleCheckmarkShape(), size: CGSize(width: 15, height: 8))

    // MARK: - Subviews

    private let bubbleView = UIView()
    private let bubbleLayer = CAShapeLayer()
    private let textLabel = UILabel()
    private let timestampLabel = UILabel()
    private let checkmarkView = UIImageView()

    // Reply quote
    private let replyContainer = UIView()
    private let replyBar = UIView()
    private let replyNameLabel = UILabel()
    private let replyTextLabel = UILabel()

    // Swipe-to-reply
    private let replyIconView = UIImageView()
    private var swipeStartX: CGFloat = 0

    // MARK: - State

    private var message: ChatMessage?
    private var actions: MessageCellActions?
    private var isOutgoing = false
    private var position: BubblePosition = .single
    private var hasReplyQuote = false
    private var preCalculatedHeight: CGFloat?

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

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

    // MARK: - Setup

    private func setupViews() {
        contentView.backgroundColor = .clear
        backgroundColor = .clear

        // Flip for inverted scroll (scale, NOT rotation — rotation flips text)
        contentView.transform = CGAffineTransform(scaleX: 1, y: -1)

        // Bubble
        bubbleLayer.fillColor = Self.outgoingColor.cgColor
        bubbleView.layer.insertSublayer(bubbleLayer, at: 0)
        contentView.addSubview(bubbleView)

        // Text
        textLabel.font = Self.textFont
        textLabel.textColor = .white
        textLabel.numberOfLines = 0
        textLabel.lineBreakMode = .byWordWrapping
        bubbleView.addSubview(textLabel)

        // Timestamp
        timestampLabel.font = Self.timestampFont
        timestampLabel.textColor = UIColor.white.withAlphaComponent(0.55)
        bubbleView.addSubview(timestampLabel)

        // Checkmark
        checkmarkView.contentMode = .scaleAspectFit
        checkmarkView.tintColor = UIColor.white.withAlphaComponent(0.55)
        bubbleView.addSubview(checkmarkView)

        // Reply quote
        replyBar.backgroundColor = .white
        replyBar.layer.cornerRadius = 1.5
        replyContainer.addSubview(replyBar)

        replyNameLabel.font = Self.replyNameFont
        replyNameLabel.textColor = .white
        replyContainer.addSubview(replyNameLabel)

        replyTextLabel.font = Self.replyTextFont
        replyTextLabel.textColor = UIColor.white.withAlphaComponent(0.8)
        replyTextLabel.lineBreakMode = .byTruncatingTail
        replyContainer.addSubview(replyTextLabel)

        replyContainer.isHidden = true
        bubbleView.addSubview(replyContainer)

        // Swipe reply icon (hidden, shown during swipe)
        replyIconView.image = UIImage(systemName: "arrowshape.turn.up.left.fill")?
            .withRenderingMode(.alwaysTemplate)
        replyIconView.tintColor = UIColor.white.withAlphaComponent(0.5)
        replyIconView.alpha = 0
        contentView.addSubview(replyIconView)

        // Long-press → Telegram context menu
        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
        longPress.minimumPressDuration = 0.35
        bubbleView.addGestureRecognizer(longPress)

        // Swipe-to-reply gesture
        let pan = UIPanGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
        pan.delegate = self
        contentView.addGestureRecognizer(pan)
    }

    // MARK: - Configure

    func configure(
        message: ChatMessage,
        timestamp: String,
        isOutgoing: Bool,
        position: BubblePosition,
        actions: MessageCellActions,
        replyName: String? = nil,
        replyText: String? = nil
    ) {
        self.message = message
        self.actions = actions
        self.isOutgoing = isOutgoing
        self.position = position

        // Text
        textLabel.text = message.text
        textLabel.textColor = .white

        // Timestamp
        timestampLabel.text = timestamp
        timestampLabel.textColor = isOutgoing
            ? UIColor.white.withAlphaComponent(0.55)
            : UIColor.white.withAlphaComponent(0.6)

        // Delivery indicator
        if isOutgoing {
            checkmarkView.isHidden = false
            switch message.deliveryStatus {
            case .delivered:
                if message.isRead {
                    checkmarkView.image = Self.doubleCheckImage.withRenderingMode(.alwaysTemplate)
                    checkmarkView.tintColor = UIColor(red: 0xA4/255, green: 0xE2/255, blue: 0xFF/255, alpha: 1)
                } else {
                    checkmarkView.image = Self.singleCheckImage.withRenderingMode(.alwaysTemplate)
                    checkmarkView.tintColor = UIColor.white.withAlphaComponent(0.55)
                }
            case .waiting:
                checkmarkView.image = UIImage(systemName: "clock")?.withRenderingMode(.alwaysTemplate)
                checkmarkView.tintColor = UIColor.white.withAlphaComponent(0.55)
            case .error:
                checkmarkView.image = UIImage(systemName: "exclamationmark.circle")?.withRenderingMode(.alwaysTemplate)
                checkmarkView.tintColor = UIColor.red
            }
        } else {
            checkmarkView.isHidden = true
        }

        // Bubble color
        bubbleLayer.fillColor = (isOutgoing ? Self.outgoingColor : Self.incomingColor).cgColor

        // Reply quote
        hasReplyQuote = (replyName != nil)
        replyContainer.isHidden = !hasReplyQuote
        if hasReplyQuote {
            replyBar.backgroundColor = isOutgoing ? .white : Self.outgoingColor
            replyNameLabel.text = replyName
            replyNameLabel.textColor = isOutgoing ? .white : Self.outgoingColor
            replyTextLabel.text = replyText ?? ""
            replyTextLabel.textColor = isOutgoing
                ? UIColor.white.withAlphaComponent(0.8)
                : UIColor.white.withAlphaComponent(0.6)
        }

        setNeedsLayout()
    }

    // MARK: - Layout

    override func layoutSubviews() {
        super.layoutSubviews()

        let bounds = contentView.bounds
        let hasTail = (position == .single || position == .bottom)
        let isTopOrSingle = (position == .single || position == .top)
        let topPad: CGFloat = isTopOrSingle ? 6 : 2
        let tailW = hasTail ? Self.tailProtrusion : 0

        // Text measurement
        let tsTrailing: CGFloat = isOutgoing ? 53 : 37
        let textMaxW = bounds.width - 11 - tsTrailing - tailW - 8
        let textSize = textLabel.sizeThatFits(CGSize(width: max(textMaxW, 50), height: .greatestFiniteMagnitude))

        // Bubble dimensions
        let bubbleContentW = 11 + textSize.width + tsTrailing
        let minW: CGFloat = isOutgoing ? 86 : 66
        let bubbleW = max(min(bubbleContentW, bounds.width - tailW - 4), minW)
        let replyH: CGFloat = hasReplyQuote ? Self.replyQuoteHeight + 5 : 0
        let bubbleH = replyH + textSize.height + 10 // 5pt top + 5pt bottom

        // Bubble X position
        let bubbleX: CGFloat
        if isOutgoing {
            bubbleX = bounds.width - bubbleW - tailW - 2
        } else {
            bubbleX = tailW + 2
        }

        bubbleView.frame = CGRect(x: bubbleX, y: topPad, width: bubbleW, height: bubbleH)

        // Bubble shape with tail
        let shapeRect: CGRect
        if hasTail {
            if isOutgoing {
                shapeRect = CGRect(x: 0, y: 0, width: bubbleW + tailW, height: bubbleH)
            } else {
                shapeRect = CGRect(x: -tailW, y: 0, width: bubbleW + tailW, height: bubbleH)
            }
        } else {
            shapeRect = CGRect(x: 0, y: 0, width: bubbleW, height: bubbleH)
        }
        bubbleLayer.path = makeBubblePath(in: shapeRect, hasTail: hasTail).cgPath
        bubbleLayer.frame = bubbleView.bounds

        // Reply quote layout
        if hasReplyQuote {
            let rX: CGFloat = 5
            replyContainer.frame = CGRect(x: rX, y: 5, width: bubbleW - 10, height: Self.replyQuoteHeight)
            replyBar.frame = CGRect(x: 0, y: 0, width: 3, height: Self.replyQuoteHeight)
            replyNameLabel.frame = CGRect(x: 9, y: 2, width: bubbleW - 24, height: 17)
            replyTextLabel.frame = CGRect(x: 9, y: 20, width: bubbleW - 24, height: 17)
        }

        // Text
        let textY: CGFloat = hasReplyQuote ? Self.replyQuoteHeight + 10 : 5
        textLabel.frame = CGRect(x: 11, y: textY, width: textSize.width, height: textSize.height)

        // Timestamp + checkmark
        let tsSize = timestampLabel.sizeThatFits(CGSize(width: 60, height: 20))
        let checkW: CGFloat = isOutgoing ? 16 : 0
        timestampLabel.frame = CGRect(
            x: bubbleW - tsSize.width - checkW - 11,
            y: bubbleH - tsSize.height - 5,
            width: tsSize.width, height: tsSize.height
        )
        if isOutgoing {
            checkmarkView.frame = CGRect(
                x: bubbleW - 11 - 14,
                y: bubbleH - tsSize.height - 4,
                width: 14, height: 10
            )
        }

        // Swipe reply icon (off-screen right of bubble)
        replyIconView.frame = CGRect(
            x: isOutgoing ? bubbleX - 30 : bubbleX + bubbleW + tailW + 8,
            y: topPad + bubbleH / 2 - 10,
            width: 20, height: 20
        )
    }

    // MARK: - Bubble Path (Figma-accurate with SVG tail)

    private func makeBubblePath(in rect: CGRect, hasTail: Bool) -> UIBezierPath {
        let r = Self.mainRadius
        let s = Self.smallRadius

        // Body rect
        let bodyRect: CGRect
        if hasTail {
            if isOutgoing {
                bodyRect = CGRect(x: rect.minX, y: rect.minY,
                                  width: rect.width - Self.tailProtrusion, height: rect.height)
            } else {
                bodyRect = CGRect(x: rect.minX + Self.tailProtrusion, y: rect.minY,
                                  width: rect.width - Self.tailProtrusion, height: rect.height)
            }
        } else {
            bodyRect = rect
        }

        // Corner radii per position
        let (tl, tr, bl, br) = cornerRadii(r: r, s: s)
        let maxR = min(bodyRect.width, bodyRect.height) / 2
        let cTL = min(tl, maxR), cTR = min(tr, maxR)
        let cBL = min(bl, maxR), cBR = min(br, maxR)

        let path = UIBezierPath()

        // Rounded rect body
        path.move(to: CGPoint(x: bodyRect.minX + cTL, y: bodyRect.minY))
        path.addLine(to: CGPoint(x: bodyRect.maxX - cTR, y: bodyRect.minY))
        path.addArc(withCenter: CGPoint(x: bodyRect.maxX - cTR, y: bodyRect.minY + cTR),
                    radius: cTR, startAngle: -.pi/2, endAngle: 0, clockwise: true)
        path.addLine(to: CGPoint(x: bodyRect.maxX, y: bodyRect.maxY - cBR))
        path.addArc(withCenter: CGPoint(x: bodyRect.maxX - cBR, y: bodyRect.maxY - cBR),
                    radius: cBR, startAngle: 0, endAngle: .pi/2, clockwise: true)
        path.addLine(to: CGPoint(x: bodyRect.minX + cBL, y: bodyRect.maxY))
        path.addArc(withCenter: CGPoint(x: bodyRect.minX + cBL, y: bodyRect.maxY - cBL),
                    radius: cBL, startAngle: .pi/2, endAngle: .pi, clockwise: true)
        path.addLine(to: CGPoint(x: bodyRect.minX, y: bodyRect.minY + cTL))
        path.addArc(withCenter: CGPoint(x: bodyRect.minX + cTL, y: bodyRect.minY + cTL),
                    radius: cTL, startAngle: .pi, endAngle: -.pi/2, clockwise: true)
        path.close()

        // Figma SVG tail (exact port from BubbleTailShape.swift)
        if hasTail {
            addFigmaTail(to: path, bodyRect: bodyRect)
        }

        return path
    }

    /// Port of BubbleTailShape.addTail — exact Figma SVG curves.
    private func addFigmaTail(to path: UIBezierPath, bodyRect: CGRect) {
        let svgStraightX: CGFloat = 5.59961
        let svgMaxY: CGFloat = 33.2305
        let sc = Self.tailProtrusion / svgStraightX
        let tailH = svgMaxY * sc

        let bodyEdge = isOutgoing ? bodyRect.maxX : bodyRect.minX
        let bottom = bodyRect.maxY
        let top = bottom - tailH
        let dir: CGFloat = isOutgoing ? 1 : -1

        func tp(_ svgX: CGFloat, _ svgY: CGFloat) -> CGPoint {
            let dx = (svgStraightX - svgX) * sc * dir
            return CGPoint(x: bodyEdge + dx, y: top + svgY * sc)
        }

        if isOutgoing {
            path.move(to: tp(5.59961, 24.2305))
            path.addCurve(to: tp(0, 33.0244),
                          controlPoint1: tp(5.42042, 28.0524), controlPoint2: tp(3.19779, 31.339))
            path.addCurve(to: tp(2.6123, 33.2305),
                          controlPoint1: tp(0.851596, 33.1596), controlPoint2: tp(1.72394, 33.2305))
            path.addCurve(to: tp(13.0293, 29.5596),
                          controlPoint1: tp(6.53776, 33.2305), controlPoint2: tp(10.1517, 31.8599))
            path.addCurve(to: tp(7.57422, 23.1719),
                          controlPoint1: tp(10.7434, 27.898), controlPoint2: tp(8.86922, 25.7134))
            path.addCurve(to: tp(5.6123, 4.2002),
                          controlPoint1: tp(5.61235, 19.3215), controlPoint2: tp(5.6123, 14.281))
            path.addLine(to: tp(5.6123, 0))
            path.addLine(to: tp(5.59961, 0))
            path.addLine(to: tp(5.59961, 24.2305))
            path.close()
        } else {
            path.move(to: tp(5.59961, 24.2305))
            path.addLine(to: tp(5.59961, 0))
            path.addLine(to: tp(5.6123, 0))
            path.addLine(to: tp(5.6123, 4.2002))
            path.addCurve(to: tp(7.57422, 23.1719),
                          controlPoint1: tp(5.6123, 14.281), controlPoint2: tp(5.61235, 19.3215))
            path.addCurve(to: tp(13.0293, 29.5596),
                          controlPoint1: tp(8.86922, 25.7134), controlPoint2: tp(10.7434, 27.898))
            path.addCurve(to: tp(2.6123, 33.2305),
                          controlPoint1: tp(10.1517, 31.8599), controlPoint2: tp(6.53776, 33.2305))
            path.addCurve(to: tp(0, 33.0244),
                          controlPoint1: tp(1.72394, 33.2305), controlPoint2: tp(0.851596, 33.1596))
            path.addCurve(to: tp(5.59961, 24.2305),
                          controlPoint1: tp(3.19779, 31.339), controlPoint2: tp(5.42042, 28.0524))
            path.close()
        }
    }

    private func cornerRadii(r: CGFloat, s: CGFloat)
        -> (tl: CGFloat, tr: CGFloat, bl: CGFloat, br: CGFloat) {
        switch position {
        case .single: return (r, r, r, r)
        case .top:    return isOutgoing ? (r, r, r, s) : (r, r, s, r)
        case .mid:    return isOutgoing ? (r, s, r, s) : (s, r, s, r)
        case .bottom: return isOutgoing ? (r, s, r, r) : (s, r, r, r)
        }
    }

    // MARK: - Context Menu (Telegram-style)

    private let contextMenuHaptic = UIImpactFeedbackGenerator(style: .medium)

    @objc private func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
        guard gesture.state == .began else { return }
        contextMenuHaptic.impactOccurred()
        presentContextMenu()
    }

    private func presentContextMenu() {
        guard let message, let actions else { return }

        // Capture snapshot from window
        guard let (snapshot, frame) = TelegramContextMenuController.captureSnapshot(of: bubbleView) else { return }

        // Build bubble mask path
        let mergeType = BubbleGeometryEngine.mergeType(for: position)
        let bubblePath = BubbleGeometryEngine.makeBezierPath(
            in: CGRect(origin: .zero, size: frame.size),
            mergeType: mergeType,
            outgoing: isOutgoing
        )

        // Build menu items
        let items = TelegramContextMenuBuilder.menuItems(
            for: message,
            actions: actions,
            isSavedMessages: false,
            isSystemAccount: false
        )

        TelegramContextMenuController.present(
            snapshot: snapshot,
            sourceFrame: frame,
            bubblePath: bubblePath,
            items: items,
            isOutgoing: isOutgoing
        )
    }

    // MARK: - Swipe to Reply

    @objc private func handleSwipe(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: contentView)

        switch gesture.state {
        case .began:
            swipeStartX = bubbleView.frame.origin.x
        case .changed:
            // Only allow swipe toward reply direction
            let dx = isOutgoing ? min(translation.x, 0) : max(translation.x, 0)
            let clamped = isOutgoing ? max(dx, -60) : min(dx, 60)
            bubbleView.transform = CGAffineTransform(translationX: clamped, y: 0)

            let progress = min(abs(clamped) / 50, 1)
            replyIconView.alpha = progress
            replyIconView.transform = CGAffineTransform(scaleX: progress, y: progress)

        case .ended, .cancelled:
            let dx = isOutgoing ? translation.x : translation.x
            if abs(dx) > 50, let message, let actions {
                // Haptic
                let generator = UIImpactFeedbackGenerator(style: .medium)
                generator.impactOccurred()
                actions.onReply(message)
            }
            UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
                self.bubbleView.transform = .identity
                self.replyIconView.alpha = 0
                self.replyIconView.transform = .identity
            }
        default:
            break
        }
    }

    // MARK: - Self-sizing

    override func preferredLayoutAttributesFitting(
        _ layoutAttributes: UICollectionViewLayoutAttributes
    ) -> UICollectionViewLayoutAttributes {
        if let h = preCalculatedHeight {
            let attrs = layoutAttributes.copy() as! UICollectionViewLayoutAttributes
            attrs.size.height = h
            return attrs
        }
        // Fallback: use automatic sizing
        return super.preferredLayoutAttributesFitting(layoutAttributes)
    }

    func setPreCalculatedHeight(_ height: CGFloat?) {
        preCalculatedHeight = height
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        preCalculatedHeight = nil
        message = nil
        actions = nil
        textLabel.text = nil
        timestampLabel.text = nil
        checkmarkView.image = nil
        replyContainer.isHidden = true
        bubbleView.transform = .identity
        replyIconView.alpha = 0
    }
}

// MARK: - UIGestureRecognizerDelegate

extension NativeTextBubbleCell: UIGestureRecognizerDelegate {
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
        let velocity = pan.velocity(in: contentView)
        // Only horizontal swipes (don't interfere with scroll)
        return abs(velocity.x) > abs(velocity.y) * 1.5
    }
}
