import ComponentLibrary
import ComposableArchitecture
import Localization
import SwiftUI
import Utilities

public struct TimedHighlightedText: View {
    @Binding var text: String
    @Binding var highlightRange: Range<String.Index>?
    @Binding var highlightingIsPastEndOfLyrics: Bool
    @Binding var replaceText: String?
    let highlightColor: Color
    let highlightStyle: LyricsHighlightStyle

    public var body: some View {
        ZStack(alignment: .leading) {
            Color.clear
            let (attributedString, isAtStart) = values()
            Text(attributedString)
                .opacity(textOpacity)
                .padding(.top, isAtStart ? 16.0 : -48.0)
                .mask {
                    VStack(spacing: .zero) {
                        Color.white
                            .opacity(0.0)
                            .frame(height: 4.0)
                        LinearGradient(colors: [
                            .white.opacity(isAtStart ? 1.0 : 0.0),
                            .white.opacity(1.0),
                        ],
                        startPoint: .top,
                        endPoint: .bottom)
                            .frame(height: 32.0)
                        Color.white
                    }
                }
        }
        .clipped()
    }

    var textOpacity: CGFloat {
        if let _ = replaceText {
            return 1.0
        } else {
            return highlightingIsPastEndOfLyrics ? 0.5 : 1.0
        }
    }

    // swiftlint:disable:next cyclomatic_complexity
    private func values() -> (AttributedString, Bool) {
        let str = text

        guard
            let range = highlightRange,
            highlightStyle != .noSelection,
            !highlightingIsPastEndOfLyrics
        else {
            if let replaceText {
                // When adding lyrics to the end of the clip,
                // with no highlighted selection,
                // show the last two lines above the added text
                let lastSixLines = text.split(separator: "\n").suffix(6)
                let startString: String
                if lastSixLines.isEmpty {
                    startString = ""
                } else {
                    startString = lastSixLines.joined(separator: "\n") + "\n\n"
                }
                let attributedStartString = AttributedString(startString)
                var bStr = AttributedString(replaceText)
                bStr.foregroundColor = highlightColor
                return (attributedStartString + bStr, lastSixLines.isEmpty)
            } else {
                return (AttributedString(str), true)
            }
        }

        let startBound: String.Index
        let endBound: String.Index
        switch highlightStyle {
        case .noSelection:
            // handled in guard; this will not be executed
            startBound = text.endIndex
            endBound = text.endIndex

        case .onlyCaptureStart:
            startBound = range.lowerBound
            endBound = text.endIndex

        case .onlyCaptureEnd:
            startBound = text.startIndex
            endBound = range.upperBound

        case .captureStartAndEnd:
            startBound = range.lowerBound
            endBound = range.upperBound
        }

        // Calculate new start index to include 2-3 lines above the highlighted range
        var showStart = startBound
        var lineCount = 0
        if highlightStyle == .onlyCaptureStart || highlightStyle == .captureStartAndEnd {
            var index = startBound
            while index > text.startIndex && lineCount < 5 {
                text.formIndex(before: &index)
                if text[index] == "\n" {
                    lineCount += 1
                    showStart = index
                }
            }
        }

        let isAtTop = lineCount < 4
        showStart = isAtTop ? text.startIndex : showStart

        // Trim the beginning of aStr to newStart
        var aStr = AttributedString(str[showStart ..< startBound])

        if let replaceText {
            var colorString = AttributedString(replaceText)
            colorString.foregroundColor = highlightColor
            aStr.append(colorString)
        } else {
            let colorRange = startBound ..< endBound
            var colorString = AttributedString(str[colorRange])
            colorString.foregroundColor = highlightColor
            aStr.append(colorString)
        }

        return (aStr, isAtTop)
    }
}
