import UIKit

// MARK: - NativeSkeletonView

/// Skeleton loading for chat message list.
/// Shows placeholder bubbles that look like real messages (same colors, tails, stretchable images)
/// with a shimmer overlay. Mix of incoming/outgoing for 1-on-1, all incoming for groups.
final class NativeSkeletonView: UIView {

    // MARK: - Chat Type

    enum ChatType { case user, group }

    var chatType: ChatType = .group {
        didSet {
            guard chatType != oldValue else { return }
            setNeedsLayout()
        }
    }

    /// Bottom inset to avoid overlapping with composer bar.
    var bottomInset: CGFloat = 0 {
        didSet {
            guard bottomInset != oldValue else { return }
            setNeedsLayout()
        }
    }

    // MARK: - Bubble Specs

    /// (widthFraction, height, isOutgoing) — outgoing only used for 1-on-1 chats.
    private struct BubbleSpec {
        let widthFrac: CGFloat
        let height: CGFloat
        let outgoing: Bool
    }

    // 1-on-1: mix of incoming/outgoing (like a real conversation)
    private static let userSpecs: [BubbleSpec] = [
        BubbleSpec(widthFrac: 0.55, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.45, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.65, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.50, height: 60, outgoing: false),
        BubbleSpec(widthFrac: 0.40, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.60, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.50, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.55, height: 60, outgoing: false),
        BubbleSpec(widthFrac: 0.45, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.50, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.60, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.40, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.55, height: 60, outgoing: true),
        BubbleSpec(widthFrac: 0.50, height: 44, outgoing: false),
    ]

    // Groups: mix of incoming/outgoing (like a real group conversation)
    private static let groupSpecs: [BubbleSpec] = [
        BubbleSpec(widthFrac: 0.47, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.58, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.50, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.69, height: 60, outgoing: false),
        BubbleSpec(widthFrac: 0.45, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.47, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.58, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.55, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.36, height: 60, outgoing: false),
        BubbleSpec(widthFrac: 0.47, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.40, height: 44, outgoing: true),
        BubbleSpec(widthFrac: 0.58, height: 44, outgoing: false),
        BubbleSpec(widthFrac: 0.69, height: 60, outgoing: false),
        BubbleSpec(widthFrac: 0.50, height: 44, outgoing: true),
    ]

    private static let avatarSize: CGFloat = 38
    private static let avatarLeftInset: CGFloat = 8
    private static let bubbleLeftInsetWithAvatar: CGFloat = 54
    private static let bubbleLeftInsetNoAvatar: CGFloat = 10
    private static let bubbleRightInset: CGFloat = 10
    private static let verticalGap: CGFloat = 4
    private static let initialBottomOffset: CGFloat = 5

    // MARK: - Shimmer Parameters

    private static let shimmerDuration: CFTimeInterval = 1.6
    private static let shimmerEffectSize: CGFloat = 280
    private static let shimmerOpacity: CGFloat = 0.14

    // MARK: - Bubble Info (for skeleton→real cell matching)

    struct BubbleInfo {
        let frame: CGRect
        let avatarFrame: CGRect?
    }

    private(set) var bubbleInfos: [BubbleInfo] = []

    // MARK: - Subviews

    private let containerView = UIView()
    private var bubbleImageViews: [UIImageView] = []
    private var avatarViews: [UIView] = []
    private var shimmerLayer: CALayer?
    private var shimmerGradients: [CAGradientLayer] = []
    private var isShimmerRunning = false

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear
        isUserInteractionEnabled = false
        containerView.frame = bounds
        containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(containerView)
    }

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

    // MARK: - Layout

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

    private func rebuildBubbles() {
        // Clean up
        bubbleImageViews.forEach { $0.removeFromSuperview() }
        avatarViews.forEach { $0.removeFromSuperview() }
        bubbleImageViews.removeAll()
        avatarViews.removeAll()
        bubbleInfos.removeAll()
        shimmerLayer?.removeFromSuperlayer()

        let width = bounds.width
        let height = bounds.height
        guard width > 0, height > 0 else { return }

        let specs = chatType == .group ? Self.groupSpecs : Self.userSpecs
        let hasAvatar = chatType == .group

        // Get real bubble images from NativeMessageCell's factory
        let bubbleImages = NativeMessageCell.currentBubbleImages

        // Avatar color
        let isDark = traitCollection.userInterfaceStyle == .dark
        let avatarColor = isDark
            ? UIColor.gray.withAlphaComponent(0.12)
            : UIColor.gray.withAlphaComponent(0.15)

        // Combined mask for shimmer overlay (bubble-shaped, not rectangular)
        let combinedMaskPath = CGMutablePath()
        let metrics = BubbleMetrics.telegram()

        // Stack from bottom up, respecting bottom inset
        var y = height - Self.initialBottomOffset - bottomInset

        for spec in specs {
            let bubbleWidth = floor(spec.widthFrac * width)
            let bubbleHeight = spec.height
            y -= bubbleHeight

            guard y > -bubbleHeight else { break }

            // Position: incoming = left, outgoing = right
            let bubbleX: CGFloat
            if spec.outgoing {
                bubbleX = width - bubbleWidth - Self.bubbleRightInset
            } else if hasAvatar {
                bubbleX = Self.bubbleLeftInsetWithAvatar
            } else {
                bubbleX = Self.bubbleLeftInsetNoAvatar
            }

            let bubbleFrame = CGRect(x: bubbleX, y: y, width: bubbleWidth, height: bubbleHeight)

            // Use real stretchable bubble image (same as NativeMessageCell)
            if let image = bubbleImages?.image(outgoing: spec.outgoing, mergeType: .none) {
                let iv = UIImageView(image: image)
                iv.frame = bubbleFrame
                containerView.addSubview(iv)
                bubbleImageViews.append(iv)
            }

            // Shimmer mask: inset path by 1pt so shimmer stays inside the raster bubble image
            // (vector BezierPath vs 9-slice raster image have slightly different anti-aliased edges)
            let maskInset: CGFloat = 1.0
            let maskRect = CGRect(origin: .zero, size: bubbleFrame.size).insetBy(dx: maskInset, dy: maskInset)
            let bubblePath = BubbleGeometryEngine.makeBezierPath(
                in: maskRect,
                mergeType: .none,
                outgoing: spec.outgoing,
                metrics: metrics
            )
            var translate = CGAffineTransform(translationX: bubbleFrame.minX, y: bubbleFrame.minY)
            if let translated = bubblePath.cgPath.copy(using: &translate) {
                combinedMaskPath.addPath(translated)
            }

            // Avatar (groups only, incoming only)
            var currentAvatarFrame: CGRect?
            if hasAvatar && !spec.outgoing {
                let avFrame = CGRect(
                    x: Self.avatarLeftInset,
                    y: y + bubbleHeight - Self.avatarSize,
                    width: Self.avatarSize,
                    height: Self.avatarSize
                )
                let avatarView = UIView(frame: avFrame)
                avatarView.backgroundColor = avatarColor
                avatarView.layer.cornerRadius = Self.avatarSize / 2
                containerView.addSubview(avatarView)
                avatarViews.append(avatarView)
                currentAvatarFrame = avFrame

                combinedMaskPath.addEllipse(in: avFrame)
            }

            bubbleInfos.append(BubbleInfo(frame: bubbleFrame, avatarFrame: currentAvatarFrame))
            y -= Self.verticalGap
        }

        // Shimmer overlay (screenBlendMode — adds brightness on top of real bubble colors)
        shimmerGradients.removeAll()
        isShimmerRunning = false

        let shimmer = makeShimmerLayer(size: bounds.size, effectSize: Self.shimmerEffectSize,
                                       color: UIColor.white.withAlphaComponent(Self.shimmerOpacity))
        let mask = CAShapeLayer()
        mask.path = combinedMaskPath
        shimmer.mask = mask
        containerView.layer.addSublayer(shimmer)
        shimmerLayer = shimmer

        if window != nil {
            startShimmerIfNeeded()
        }
    }

    // MARK: - Shimmer Layer Factory

    private func makeShimmerLayer(size: CGSize, effectSize: CGFloat, color: UIColor) -> CALayer {
        let container = CALayer()
        container.frame = CGRect(origin: .zero, size: size)
        container.compositingFilter = "screenBlendMode"

        let gradient = CAGradientLayer()
        gradient.colors = [color.withAlphaComponent(0).cgColor, color.cgColor, color.withAlphaComponent(0).cgColor]
        gradient.locations = [0.0, 0.5, 1.0]
        gradient.startPoint = CGPoint(x: 0, y: 0.5)
        gradient.endPoint = CGPoint(x: 1, y: 0.5)
        gradient.frame = CGRect(x: -effectSize, y: 0, width: effectSize, height: size.height)

        container.addSublayer(gradient)
        shimmerGradients.append(gradient)
        return container
    }

    // MARK: - Shimmer Lifecycle

    override func didMoveToWindow() {
        super.didMoveToWindow()
        if window != nil { startShimmerIfNeeded() } else { stopShimmer() }
    }

    private func startShimmerIfNeeded() {
        guard !isShimmerRunning, !shimmerGradients.isEmpty else { return }
        isShimmerRunning = true
        let width = bounds.width
        guard width > 0 else { return }

        for gradient in shimmerGradients {
            let effectSize = gradient.bounds.width
            let anim = CABasicAnimation(keyPath: "position.x")
            anim.fromValue = -effectSize / 2
            anim.toValue = width + effectSize / 2
            anim.duration = Self.shimmerDuration
            anim.timingFunction = CAMediaTimingFunction(name: .easeOut)
            anim.repeatCount = .infinity
            gradient.add(anim, forKey: "shimmer")
        }
    }

    private func stopShimmer() {
        guard isShimmerRunning else { return }
        isShimmerRunning = false
        for gradient in shimmerGradients { gradient.removeAnimation(forKey: "shimmer") }
    }

    // MARK: - Fade Out (fallback)

    func animateOut(completion: @escaping () -> Void) {
        stopShimmer()
        CATransaction.begin()
        CATransaction.setAnimationDuration(0.15)
        CATransaction.setCompletionBlock(completion)
        layer.opacity = 0
        CATransaction.commit()
    }

    // MARK: - AnimateOut With Cell Matching

    func animateOutWithCells(
        _ cells: [(cell: UIView, isOutgoing: Bool)],
        heightNorm: CGFloat,
        completion: @escaping () -> Void
    ) {
        stopShimmer()

        // Fade out skeleton
        CATransaction.begin()
        CATransaction.setAnimationDuration(0.15)
        CATransaction.setCompletionBlock(completion)
        layer.opacity = 0
        CATransaction.commit()

        // Per-cell entrance animation — additive, on cell.layer
        let now = CACurrentMediaTime()

        for info in cells {
            let cellLayer = info.cell.layer
            let isIncoming = !info.isOutgoing

            let cellFrameInSelf = convert(info.cell.frame, from: info.cell.superview)
            let delayFactor = max(0, cellFrameInSelf.minY / max(heightNorm, 1))
            let delay = Double(delayFactor) * 0.1

            // Alpha: 0→1
            let fadeIn = CABasicAnimation(keyPath: "opacity")
            fadeIn.fromValue = 0.0
            fadeIn.toValue = 1.0
            fadeIn.duration = 0.2
            fadeIn.beginTime = now + delay
            fadeIn.fillMode = .backwards
            fadeIn.isRemovedOnCompletion = true
            cellLayer.add(fadeIn, forKey: "skeletonFadeIn")
            cellLayer.opacity = 1.0

            // Position: additive spring
            let xOffset: CGFloat = isIncoming ? 30 : -30

            let posX = CASpringAnimation(keyPath: "position.x")
            posX.fromValue = xOffset
            posX.toValue = 0.0
            posX.isAdditive = true
            posX.stiffness = 555.0
            posX.damping = 47.0
            posX.mass = 1.0
            posX.beginTime = now + delay
            posX.fillMode = .backwards
            posX.duration = posX.settlingDuration
            cellLayer.add(posX, forKey: "skeletonPositionX")

            let posY = CASpringAnimation(keyPath: "position.y")
            posY.fromValue = 30.0
            posY.toValue = 0.0
            posY.isAdditive = true
            posY.stiffness = 555.0
            posY.damping = 47.0
            posY.mass = 1.0
            posY.beginTime = now + delay
            posY.fillMode = .backwards
            posY.duration = posY.settlingDuration
            cellLayer.add(posY, forKey: "skeletonPositionY")

            // Scale: additive
            let scaleAnim = CABasicAnimation(keyPath: "transform")
            scaleAnim.fromValue = CATransform3DMakeScale(0.85, 0.85, 1.0)
            scaleAnim.toValue = CATransform3DIdentity
            scaleAnim.isAdditive = true
            scaleAnim.duration = 0.35
            scaleAnim.timingFunction = CAMediaTimingFunction(name: .easeOut)
            scaleAnim.beginTime = now + delay
            scaleAnim.fillMode = .backwards
            scaleAnim.isRemovedOnCompletion = true
            cellLayer.add(scaleAnim, forKey: "skeletonScale")
        }
    }
}
