import SwiftUI
import UIKit

enum BubblePosition: Sendable, Equatable {
    case single, top, mid, bottom
}

enum BubbleMergeType: Sendable, Hashable {
    case none
    case top(side: Bool)
    case bottom
    case both
    case side
    case extracted
}

struct BubbleMetrics: Sendable {
    let mainRadius: CGFloat
    let auxiliaryRadius: CGFloat
    let tailProtrusion: CGFloat
    let defaultSpacing: CGFloat
    let mergedSpacing: CGFloat
    let textInsets: UIEdgeInsets
    let mediaStatusInsets: UIEdgeInsets

    static func telegram(screenScale: CGFloat = max(UIScreen.main.scale, 1)) -> BubbleMetrics {
        let screenPixel = 1.0 / screenScale
        return BubbleMetrics(
            mainRadius: 16,
            auxiliaryRadius: 8,
            tailProtrusion: 6,
            defaultSpacing: 2 + screenPixel,
            mergedSpacing: 0,
            textInsets: UIEdgeInsets(top: 6 + screenPixel, left: 11, bottom: 6 - screenPixel, right: 11),
            mediaStatusInsets: UIEdgeInsets(top: 2, left: 7, bottom: 2, right: 7)
        )
    }
}

enum BubbleGeometryEngine {

    static func mergeType(for position: BubblePosition) -> BubbleMergeType {
        switch position {
        case .single:
            return .none
        case .top:
            return .top(side: false)
        case .mid:
            return .both
        case .bottom:
            return .bottom
        }
    }

    static func hasTail(for mergeType: BubbleMergeType) -> Bool {
        switch mergeType {
        case .none, .bottom:
            return true
        case .top, .both, .side, .extracted:
            return false
        }
    }

    static func hasTail(for position: BubblePosition) -> Bool {
        hasTail(for: mergeType(for: position))
    }

    static func cornerRadii(
        mergeType: BubbleMergeType,
        outgoing: Bool,
        metrics: BubbleMetrics
    ) -> (topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
        var topLeftRadius: CGFloat
        var topRightRadius: CGFloat
        var bottomLeftRadius: CGFloat
        var bottomRightRadius: CGFloat

        switch mergeType {
        case .none:
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.mainRadius
            bottomLeftRadius = metrics.mainRadius
            bottomRightRadius = metrics.mainRadius
        case .both:
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.auxiliaryRadius
            bottomLeftRadius = metrics.mainRadius
            bottomRightRadius = metrics.auxiliaryRadius
        case .bottom:
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.auxiliaryRadius
            bottomLeftRadius = metrics.mainRadius
            bottomRightRadius = metrics.mainRadius
        case .side:
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.mainRadius
            bottomLeftRadius = metrics.auxiliaryRadius
            bottomRightRadius = metrics.auxiliaryRadius
        case let .top(side):
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.mainRadius
            bottomLeftRadius = side ? metrics.auxiliaryRadius : metrics.mainRadius
            bottomRightRadius = metrics.auxiliaryRadius
        case .extracted:
            topLeftRadius = metrics.mainRadius
            topRightRadius = metrics.mainRadius
            bottomLeftRadius = metrics.mainRadius
            bottomRightRadius = metrics.mainRadius
        }

        if outgoing {
            return (topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius)
        }
        return (topRightRadius, topLeftRadius, bottomRightRadius, bottomLeftRadius)
    }

    static func bodyRect(
        in rect: CGRect,
        mergeType: BubbleMergeType,
        outgoing: Bool,
        metrics: BubbleMetrics
    ) -> CGRect {
        guard hasTail(for: mergeType) else {
            return rect
        }
        if outgoing {
            return CGRect(
                x: rect.minX,
                y: rect.minY,
                width: rect.width - metrics.tailProtrusion,
                height: rect.height
            )
        }
        return CGRect(
            x: rect.minX + metrics.tailProtrusion,
            y: rect.minY,
            width: rect.width - metrics.tailProtrusion,
            height: rect.height
        )
    }

    static func makeBezierPath(
        in rect: CGRect,
        mergeType: BubbleMergeType,
        outgoing: Bool,
        metrics: BubbleMetrics = .telegram()
    ) -> UIBezierPath {
        let path = UIBezierPath()
        let bodyRect = bodyRect(in: rect, mergeType: mergeType, outgoing: outgoing, metrics: metrics)
        let radii = cornerRadii(mergeType: mergeType, outgoing: outgoing, metrics: metrics)

        let maxRadius = min(bodyRect.width, bodyRect.height) / 2
        let cTL = min(radii.topLeft, maxRadius)
        let cTR = min(radii.topRight, maxRadius)
        let cBL = min(radii.bottomLeft, maxRadius)
        let cBR = min(radii.bottomRight, maxRadius)

        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()

        if hasTail(for: mergeType) {
            addFigmaTail(
                to: path,
                bodyRect: bodyRect,
                outgoing: outgoing,
                tailProtrusion: metrics.tailProtrusion
            )
        }

        return path
    }

    static func makeCGPath(
        in rect: CGRect,
        mergeType: BubbleMergeType,
        outgoing: Bool,
        metrics: BubbleMetrics = .telegram()
    ) -> CGPath {
        makeBezierPath(in: rect, mergeType: mergeType, outgoing: outgoing, metrics: metrics).cgPath
    }

    static func makeSwiftUIPath(
        in rect: CGRect,
        mergeType: BubbleMergeType,
        outgoing: Bool,
        metrics: BubbleMetrics = .telegram()
    ) -> Path {
        Path(makeBezierPath(in: rect, mergeType: mergeType, outgoing: outgoing, metrics: metrics).cgPath)
    }

}
