import Foundation
import SpriteKit
import SwiftUI

// swiftlint:disable function_parameter_count file_length

public enum LyricsPreset: Equatable {
    case blankTimeUpdateOnly
    case block(maxLines: Int?) // display wrapped blocks up to maxLines (nil = unlimited)
    case omniplayer // display current line for omniplayer v3 lyrics
    case twoLine // current + next
    case threeLine // previous, current, next
    case debug // current word debug
}

public enum LyricsAlignment: Equatable {
    public enum Horizontal: Equatable {
        case leading
        case center
        case trailing
    }
}

/// SwiftUI wrapper for a live SpriteKit lyrics scene.
public struct LyricsOverlay: UIViewRepresentable, Identifiable {
    public var id: String
    public var lyricLines: [LyricsLine]
    public var startTime: TimeInterval = 0.0
    public var endTime: TimeInterval = 1.0
    public var elapsedTime: TimeInterval
    public var preset: LyricsPreset = .block(maxLines: nil)
    public var fontResource: TypographyV1.FontResource = .neueMontrealMedium
    public var fontSize: CGFloat = 64
    public var fontColor: UIColor = .white
    public var maxLineWidth: CGFloat = 900
    public var isPaused: Bool
    public var isScrubbing: Bool = false
    public let size: CGSize
    public var horizontalAlignment: LyricsAlignment.Horizontal = .center
    /*
        (ID, deltaTime, startTimeOffset)

        deltaTime difference in time between last frame and this frame
        startTimeOffset is the given offset of startTime
     */
    public var onTimeUpdate: (String, TimeInterval, TimeInterval) -> Void

    public init(
        id: String,
        size: CGSize,
        lyricLines: [LyricsLine],
        startTime: TimeInterval,
        endTime: TimeInterval,
        elapsedTime: TimeInterval,
        preset: LyricsPreset,
        fontResource: TypographyV1.FontResource,
        fontSize: CGFloat,
        fontColor: UIColor = .white,
        maxLineWidth: CGFloat,
        isPaused: Bool,
        isScrubbing: Bool = false,
        horizontalAlignment: LyricsAlignment.Horizontal = .center,
        onTimeUpdate: @escaping (String, TimeInterval, TimeInterval) -> Void
    ) {
        self.id = id
        self.lyricLines = lyricLines
        self.startTime = startTime
        self.endTime = endTime
        self.elapsedTime = elapsedTime
        self.preset = preset
        self.fontResource = fontResource
        self.fontSize = fontSize
        self.fontColor = fontColor
        self.maxLineWidth = maxLineWidth
        self.isPaused = isPaused
        self.isScrubbing = isScrubbing
        self.size = size
        self.horizontalAlignment = horizontalAlignment
        self.onTimeUpdate = onTimeUpdate
    }

    public func makeUIView(context _: Context) -> SKView {
        let skView = SKView()
        skView.ignoresSiblingOrder = true
        skView.backgroundColor = .clear
        let scene = LyricsScene(size: size)
        scene.scaleMode = .resizeFill
        skView.scene?.backgroundColor = .clear
        scene.configure(
            id: id,
            lyricLines: lyricLines,
            startTime: startTime,
            endTime: endTime,
            elapsedTime: elapsedTime,
            preset: preset,
            fontResource: fontResource,
            fontSize: fontSize,
            fontColor: fontColor,
            maxLineWidth: maxLineWidth,
            horizontalAlignment: horizontalAlignment,
            isScrubbing: isScrubbing
        )
        scene.onTimeUpdate = onTimeUpdate
        skView.presentScene(scene)
        scene.isPaused = isPaused
        return skView
    }

    public func updateUIView(_ uiView: SKView, context _: Context) {
        if let scene = uiView.scene as? LyricsScene {
            scene.configure(
                id: id,
                lyricLines: lyricLines,
                startTime: startTime,
                endTime: endTime,
                elapsedTime: elapsedTime,
                preset: preset,
                fontResource: fontResource,
                fontSize: fontSize,
                fontColor: fontColor,
                maxLineWidth: maxLineWidth,
                horizontalAlignment: horizontalAlignment,
                isScrubbing: isScrubbing
            )
            scene.isPaused = isPaused
        }
    }
}

/// SpriteKit scene that renders lyrics per the chosen preset.
class LyricsScene: SKScene {
    private var id: String = ""
    private var lyricLines: [LyricsLine] = []
    private var startTime: TimeInterval = 0.0
    private var endTime: TimeInterval = 1.0
    private var preset: LyricsPreset = .block(maxLines: nil)
    private var fontResource: TypographyV1.FontResource = .neueMontrealMedium
    private var fontName: String = "Helvetica"
    private var uiFont: UIFont?
    private var fontSize: CGFloat = 64
    private var fontColor: UIColor = .white
    private var maxLineWidth: CGFloat = 900
    private var lastActiveLyricIndex = 0
    private var nodes: [SKLabelNode] = []
    private var debugNode: SKLabelNode?
    private var horizontalAlignment: LyricsAlignment.Horizontal = .center

    private var lastTimeSeen: TimeInterval?
    private var elapsedTime: TimeInterval = .zero
    private var isScrubbing: Bool = false

    // First actual lyric for pre-showing before they start
    private var firstLyricIndex: Int?
    private var firstLyricStartTime: TimeInterval = 0

    private var isPerformingScrollAnimation: Bool = false

    /* OverlayID, DeltaTime, StartTime */
    var onTimeUpdate: (String, TimeInterval, TimeInterval) -> Void = { _, _, _ in }

    func configure(
        id: String,
        lyricLines: [LyricsLine],
        startTime: TimeInterval,
        endTime: TimeInterval,
        elapsedTime: TimeInterval,
        preset: LyricsPreset,
        fontResource: TypographyV1.FontResource,
        fontSize: CGFloat,
        fontColor: UIColor = .white,
        maxLineWidth: CGFloat,
        horizontalAlignment: LyricsAlignment.Horizontal,
        isScrubbing: Bool = false
    ) {
        let presetChanged = self.preset != preset
        let alignmentChanged = self.horizontalAlignment != horizontalAlignment

        self.id = id
        self.lyricLines = lyricLines
        self.startTime = startTime
        self.endTime = endTime
        self.elapsedTime = elapsedTime
        self.preset = preset
        self.fontResource = fontResource
        self.fontName = fontResource.fontName
        self.uiFont = fontResource.uiFont(fontSize)
        self.fontSize = fontSize
        self.fontColor = fontColor
        self.maxLineWidth = maxLineWidth
        self.horizontalAlignment = horizontalAlignment
        self.isScrubbing = isScrubbing

        setFirstLyric()

        if presetChanged || alignmentChanged || (nodes.isEmpty && debugNode == nil) {
            setupNodes()
        }
    }

    var totalDuration: TimeInterval {
        return max(endTime - startTime, 1.0 / 30.0)
    }

    override func didMove(to _: SKView) {
        backgroundColor = .clear
        setupNodes()
    }

    private func setupNodes() {
        removeAllChildren()
        nodes.removeAll()
        debugNode = nil

        let horizontalAlignment: SKLabelHorizontalAlignmentMode = {
            switch self.horizontalAlignment {
            case .leading: return .left
            case .center: return .center
            case .trailing: return .right
            }
        }()

        switch preset {
        case .blankTimeUpdateOnly:
            break

        case .debug:
            let node = SKLabelNode(fontNamed: fontName)
            node.fontSize = fontSize * 1.5
            node.horizontalAlignmentMode = horizontalAlignment
            debugNode = node
            addChild(node)

        case .threeLine:
            // prev, current, next
            for _ in 0 ..< 3 {
                let node = SKLabelNode(fontNamed: fontName)
                node.horizontalAlignmentMode = horizontalAlignment
                nodes.append(node)
                addChild(node)
            }

        case .twoLine:
            // current + next
            for _ in 0 ..< 2 {
                let node = SKLabelNode(fontNamed: fontName)
                node.horizontalAlignmentMode = horizontalAlignment
                nodes.append(node)
                addChild(node)
            }

        case .omniplayer:
            // current line only
            let node = SKLabelNode(fontNamed: fontName)
            node.horizontalAlignmentMode = horizontalAlignment
            nodes.append(node)
            addChild(node)

        case .block(let maxLines):
            // wrapped blocks up to maxLines (default 4)
            let count = maxLines ?? 4
            for _ in 0 ..< count {
                let node = SKLabelNode(fontNamed: fontName)
                node.horizontalAlignmentMode = horizontalAlignment
                nodes.append(node)
                addChild(node)
            }
        }
    }

    override func update(_ currentTime: TimeInterval) {
        var delta: TimeInterval = .zero
        if let lastTimeSeen {
            delta = currentTime - lastTimeSeen
        }

        lastTimeSeen = currentTime
        onTimeUpdate(id, delta, startTime)

        let zeroBaseElapsedTime = elapsedTime.truncatingRemainder(dividingBy: totalDuration)
        let localTime = zeroBaseElapsedTime + startTime

        switch preset {
        case .blankTimeUpdateOnly:
            break
        case .debug:
            updateDebug(at: localTime)
        case .threeLine:
            updateThreeLine(at: localTime)
        case .twoLine:
            updateTwoLine(at: localTime)
        case .omniplayer:
            updateOmniplayer(at: localTime)
        case .block(let maxLines):
            updateBlock(at: localTime, maxLines: maxLines)
        }
    }
}

extension LyricsScene {
    // MARK: - Debug Mode

    private func updateDebug(at time: TimeInterval) {
        guard let token = lyricLines
            .flatMap({ $0.words })
            .first(where: { $0.startTime <= time && time < $0.endTime }),
            let node = debugNode else { return }
        node.text = token.text
        node.fontColor = fontColor
        node.position = CGPoint(x: xPosition(), y: size.height / 2)
    }

    // MARK: - Block Mode

    private func updateBlock(at time: TimeInterval, maxLines: Int?) {
        guard !lyricLines.isEmpty, !nodes.isEmpty else { return }

        // Find current line
        let currentLineIndex = getCurrentLyricIndex(for: time) ?? lastActiveLyricIndex
        lastActiveLyricIndex = currentLineIndex

        // Get visible lines
        let pageSize = maxLines ?? 4
        let page = currentLineIndex / pageSize
        let start = page * pageSize
        let end = min(start + pageSize, lyricLines.count)
        let visibleLines = Array(lyricLines[start ..< end])

        // Combine all words from all visible lines
        var allWords: [WordToken] = []
        for line in visibleLines {
            let lineWords = reconstructWords(from: line.words)
            allWords.append(contentsOf: lineWords)
        }

        let allWrappedWords = wrapLines(allWords, maxLines: nil)

        // Layout wrapped lines
        let lineHeight = fontSize * 1.5
        let totalHeight = CGFloat(allWrappedWords.count) * lineHeight
        var y = (size.height + totalHeight) / 2 - lineHeight

        for (i, node) in nodes.enumerated() {
            if i < allWrappedWords.count {
                let wordGroup = allWrappedWords[i]
                let (text, attr) = createKaraokeTextFromWords(wordGroup, time: time)
                node.isHidden = false
                node.fontSize = fontSize
                node.position = CGPoint(x: xPosition(), y: y)
                node.text = text
                node.attributedText = attr
            } else {
                node.isHidden = true
            }
            y -= lineHeight
        }
    }

    // MARK: - Three-Line Mode

    private func updateThreeLine(at time: TimeInterval) {
        guard nodes.count == 3 else { return }

        // if there are no lyric lines, hide everything and bail
        if lyricLines.isEmpty {
            nodes.forEach { $0.isHidden = true }
            return
        }

        // figure out which line is "current
        let idx = getCurrentLyricIndex(for: time) ?? lastActiveLyricIndex
        lastActiveLyricIndex = idx

        // optional prev/next indices
        let prevIdx: Int? = idx > 0 ? idx - 1 : nil
        let nextIdx: Int? = idx < lyricLines.count - 1 ? idx + 1 : nil

        let centerY = size.height / 2
        let offsetY = fontSize * 3.0
        let positions: [CGFloat] = [
            centerY + offsetY, // prev slot
            centerY, // curr slot
            centerY - offsetY, // next slot
        ]

        let slots: [Int?] = [prevIdx, idx, nextIdx]

        for (i, node) in nodes.enumerated() {
            guard let lineIdx = slots[i] else {
                node.isHidden = true
                continue
            }

            let line = lyricLines[lineIdx]
            let isCurrent = (lineIdx == idx)

            updateNodeContent(node, line: line, scale: isCurrent ? 1.0 : 0.75, alpha: isCurrent ? 1.0 : 0.5)
            node.position = CGPoint(x: xPosition(), y: positions[i])
        }
    }

    // MARK: - Two-Line Mode

    private func updateTwoLine(at time: TimeInterval) {
        guard nodes.count == 2 else { return }

        // if there are no lyric lines, hide everything and bail
        if lyricLines.isEmpty {
            nodes.forEach { $0.isHidden = true }
            return
        }

        // figure out which line is "current
        let idx = getCurrentLyricIndex(for: time) ?? lastActiveLyricIndex
        lastActiveLyricIndex = idx

        // optional prev/next indices
        let nextIdx: Int? = idx < lyricLines.count - 1 ? idx + 1 : nil

        let centerY = size.height / 2
        let offsetY = fontSize * 3.0
        let positions: [CGFloat] = [
            centerY, // curr slot
            centerY - offsetY, // next slot
        ]

        let slots: [Int?] = [idx, nextIdx]

        for (i, node) in nodes.enumerated() {
            guard let lineIdx = slots[i] else {
                node.isHidden = true
                continue
            }

            let line = lyricLines[lineIdx]
            let isCurrent = (lineIdx == idx)

            updateNodeContent(node, line: line, scale: isCurrent ? 1.0 : 0.75, alpha: isCurrent ? 1.0 : 0.75)
            node.position = CGPoint(x: xPosition(), y: positions[i])
        }
    }

    // MARK: - Omniplayer (One-Line) Mode

    private func updateOmniplayer(at time: TimeInterval) {
        guard nodes.count == 1 else { return }

        if lyricLines.isEmpty {
            nodes.forEach { $0.isHidden = true }
            return
        }

        guard let currentIndex = getCurrentLyricIndex(for: time) else {
            nodes[0].isHidden = true
            return
        }

        let previousIndex = lastActiveLyricIndex
        lastActiveLyricIndex = currentIndex
        let currentLine = lyricLines[currentIndex]

        let isShowingEarly = currentIndex == firstLyricIndex && time < firstLyricStartTime
        let opacity = isShowingEarly ? 0.4 : 1.0

        if currentIndex != previousIndex {
            if isScrubbing {
                updateNodeContentImmediately(line: currentLine, opacity: opacity)
            } else if !isPerformingScrollAnimation {
                performScrollAnimation(line: currentLine, opacity: opacity)
            }
        } else if !isPerformingScrollAnimation {
            updateNodeContentImmediately(line: currentLine, opacity: opacity)
        }
    }
}

private extension LyricsScene {
    func createKaraokeTextFromWords(
        _ words: [WordToken],
        time: TimeInterval
    ) -> (String, NSAttributedString) {
        let text = words.map { $0.text }.joined(separator: " ")
        let attr = NSMutableAttributedString(string: text, attributes: [.font: uiFont ?? .init()])

        // Apply karaoke animation to words
        var cursor = 0
        for word in words {
            let range = NSRange(location: cursor, length: word.text.count)
            let opacity = time >= word.startTime ? 1.0 : 0.5
            attr.addAttribute(.foregroundColor, value: fontColor.withAlphaComponent(opacity), range: range)
            cursor += word.text.count + 1
        }
        return (text, attr)
    }

    func reconstructWords(
        from fragments: [WordToken]
    ) -> [WordToken] {
        var completeWords: [WordToken] = []
        var currentWord = ""
        var currentStartTime: TimeInterval = 0
        var currentEndTime: TimeInterval = 0

        for fragment in fragments {
            let text = fragment.text

            // If fragment starts with space, it's a new word
            if text.hasPrefix(" ") {
                // Save previous word if it exists
                if !currentWord.isEmpty {
                    completeWords.append(WordToken(
                        text: currentWord,
                        startTime: currentStartTime,
                        endTime: currentEndTime
                    ))
                }

                // Start new word
                currentWord = text.trimmingCharacters(in: .whitespaces)
                currentStartTime = fragment.startTime
                currentEndTime = fragment.endTime
            } else {
                // Continue current word
                if currentWord.isEmpty {
                    currentStartTime = fragment.startTime
                }
                currentWord += text
                currentEndTime = fragment.endTime
            }
        }

        // Add the last word
        if !currentWord.isEmpty {
            completeWords.append(WordToken(
                text: currentWord,
                startTime: currentStartTime,
                endTime: currentEndTime
            ))
        }
        return completeWords
    }
}

private extension LyricsScene {
    func updateNodeContentImmediately(line: LyricsLine, opacity: CGFloat) {
        updateNodeContent(nodes[0], line: line, maxLines: 2, alpha: opacity)
        nodes[0].position = CGPoint(x: xPosition(), y: size.height / 2)
    }

    func performScrollAnimation(line: LyricsLine, opacity: CGFloat) {
        isPerformingScrollAnimation = true

        let newLyricNode = createTemporaryNode(for: line)
        addChild(newLyricNode)

        let animationDuration: TimeInterval = 0.2
        let currentAnimation = createExitAnimation(duration: animationDuration)
        let newAnimation = createEntranceAnimation(duration: animationDuration, opacity: opacity)

        nodes[0].run(currentAnimation)
        newLyricNode.run(newAnimation) { [weak self] in
            self?.replaceMainNode(with: newLyricNode)
            self?.isPerformingScrollAnimation = false
        }
    }

    func createTemporaryNode(for line: LyricsLine) -> SKLabelNode {
        let node = SKLabelNode(fontNamed: fontName)
        node.horizontalAlignmentMode = nodes[0].horizontalAlignmentMode
        node.fontSize = fontSize
        node.fontColor = fontColor
        node.alpha = 0.0
        node.position = CGPoint(x: xPosition(), y: size.height / 2 - 60)
        node.zPosition = nodes[0].zPosition + 1

        updateNodeContent(node, line: line, maxLines: 2, alpha: 1.0)
        return node
    }

    func createExitAnimation(duration: TimeInterval) -> SKAction {
        let fadeOut = SKAction.fadeAlpha(to: 0, duration: duration)
        let animation = SKAction.group([fadeOut])
        animation.timingMode = .easeOut
        return animation
    }

    func createEntranceAnimation(duration: TimeInterval, opacity: CGFloat) -> SKAction {
        let slideUp = SKAction.moveBy(x: 0, y: 60, duration: duration)
        let fadeIn = SKAction.fadeAlpha(to: opacity, duration: duration)
        let animation = SKAction.group([slideUp, fadeIn])
        animation.timingMode = .easeOut
        return animation
    }

    private func replaceMainNode(with newNode: SKLabelNode) {
        nodes[0].removeFromParent()
        nodes[0] = newNode
        nodes[0].zPosition = 0
        nodes[0].position = CGPoint(x: xPosition(), y: size.height / 2)
    }
}

private extension LyricsScene {
    /// Finds and sets the first lyric of the clip to be pre-showed as dimmed text before the track starts
    func setFirstLyric() {
        for (index, line) in lyricLines.enumerated() {
            if !line.text.isEmpty {
                firstLyricIndex = index
                firstLyricStartTime = line.startTime
                return
            }
        }
        firstLyricIndex = nil
    }
}

private extension LyricsScene {
    /// Gets the current lyric index to display
    func getCurrentLyricIndex(for time: TimeInterval) -> Int? {
        guard !lyricLines.isEmpty else { return nil }
        let shouldShowFirstLyricEarly = firstLyricIndex != nil && time < firstLyricStartTime
        if shouldShowFirstLyricEarly {
            return firstLyricIndex
        }

        if let activeIndex = findActiveLyricIndex(for: time) {
            return activeIndex
        }

        return findNearestLyricIndex(for: time)
    }

    func findActiveLyricIndex(for time: TimeInterval) -> Int? {
        for (index, line) in lyricLines.enumerated() {
            if line.startTime <= time, time < line.endTime {
                return index
            }
        }
        return nil
    }

    func findNearestLyricIndex(for time: TimeInterval) -> Int? {
        guard !lyricLines.isEmpty else { return nil }

        let isBeforeFirstLyric = time < lyricLines[0].startTime
        if isBeforeFirstLyric {
            return 0
        }

        let isAfterLastLyric = time >= lyricLines.last!.endTime
        if isAfterLastLyric {
            return lyricLines.count - 1
        }

        for (index, line) in lyricLines.enumerated() {
            let isCurrentLyric = line.startTime <= time && time < line.endTime
            if isCurrentLyric {
                return index
            }

            if time < line.startTime {
                let hasPreviousLyric = index > 0
                if hasPreviousLyric {
                    let previousLine = lyricLines[index - 1]
                    let timeSincePreviousEnd = time - previousLine.endTime
                    let timeUntilNextStart = line.startTime - time
                    let isCloserToPrevious = timeSincePreviousEnd < timeUntilNextStart
                    return isCloserToPrevious ? index - 1 : index
                } else {
                    return index
                }
            }
        }
        return lyricLines.count - 1
    }
}

private extension LyricsScene {
    func updateNodeContent(
        _ node: SKLabelNode,
        line: LyricsLine,
        maxLines: Int? = nil,
        scale: CGFloat = 1.0,
        alpha: CGFloat = 1.0
    ) {
        let actualMaxLines = maxLines ?? Int.max

        let words = line.text.split(separator: " ").map { word in
            // Find matching word timing from character tokens
            let matchingToken = line.words.first { $0.text.trimmingCharacters(in: .whitespaces) == word }
            return WordToken(
                text: String(word),
                startTime: matchingToken?.startTime ?? 0,
                endTime: matchingToken?.endTime ?? 0
            )
        }

        let wrappedLines = wrapLines(words, maxLines: actualMaxLines)
        let fullText = wrappedLines
            .map { $0.map(\.text).joined(separator: " ") }
            .joined(separator: "\n")

        guard !fullText.isEmpty else {
            node.isHidden = true
            return
        }
        node.isHidden = false
        node.numberOfLines = maxLines ?? 0
        node.preferredMaxLayoutWidth = maxLineWidth
        node.lineBreakMode = .byWordWrapping
        node.verticalAlignmentMode = .center

        let attributedString = NSMutableAttributedString(string: fullText)
        let textRange = NSRange(location: 0, length: fullText.count)

        // Set paragraph alignment
        let paragraphStyle = NSMutableParagraphStyle()
        switch horizontalAlignment {
        case .leading: paragraphStyle.alignment = .left
        case .center: paragraphStyle.alignment = .center
        case .trailing: paragraphStyle.alignment = .right
        }

        // Set font style with improved scaling
        if let font = uiFont?.withSize(fontSize * scale) {
            attributedString.addAttributes([
                .font: font,
                .foregroundColor: fontColor.withAlphaComponent(alpha),
                .paragraphStyle: paragraphStyle,
            ], range: textRange)
        }
        node.attributedText = attributedString
    }

    func attributedLine(
        _ words: [WordToken],
        at time: TimeInterval
    ) -> (String, NSAttributedString) {
        // join your words into one string
        let text = words.map { $0.text }.joined(separator: " ")
        // base font for the entire string
        let baseFont = uiFont ?? .init()
        // Define the attributes for the bold and colored text
        let attr = NSMutableAttributedString(
            string: text,
            attributes: [.font: baseFont]
        )

        // walk each word, styling only the "current" one full‑bright
        var cursor = 0
        for w in words {
            let range = NSRange(location: cursor, length: w.text.count)
            // highlight only while we're within the word's start–end window
            let isCurrent = (time >= w.startTime)
            let alpha: CGFloat = isCurrent ? 1.0 : 0.5
            attr.addAttribute(
                .foregroundColor,
                value: fontColor.withAlphaComponent(alpha),
                range: range
            )
            // advance past this word + the joining space
            cursor += w.text.count + 1
        }

        return (text, attr)
    }
}

private extension LyricsScene {
    func wrapLines(_ words: [WordToken], maxLines: Int?) -> [[WordToken]] {
        var lines: [[WordToken]] = []
        var current: [WordToken] = []
        var widthAccum: CGFloat = 0
        let spaceW = " ".size(withAttributes: [.font: UIFont(name: fontName, size: fontSize)!]).width
        for w in words {
            let wW = w.text.size(withAttributes: [.font: UIFont(name: fontName, size: fontSize)!]).width
            let wouldExceed = !current.isEmpty && (widthAccum + spaceW + wW > maxLineWidth)
            if wouldExceed {
                lines.append(current)
                if let m = maxLines, lines.count >= m { break }
                current = []
                widthAccum = 0
            }
            current.append(w)
            widthAccum += (current.count > 1 ? spaceW : 0) + wW
        }
        if !current.isEmpty, maxLines == nil || lines.count < maxLines! {
            lines.append(current)
        }
        return lines
    }

    func xPosition() -> CGFloat {
        switch horizontalAlignment {
        case .leading:
            return 0
        case .center:
            return size.width / 2
        case .trailing:
            return size.width
        }
    }
}
