import APIClient
import ComponentLibrary
import ComposableArchitecture
import Localization
import SwiftUI
import Utilities

@Reducer
public struct LyricsSelection {
    @ObservableState
    public struct State: Equatable {
        var lyrics: String
        var replacementLyrics: String
        var lyricsSelectionRange: Range<String.Index>?
        var lyricsAlignedWords: [AlignedLyric] = []
        var highlightedTextTags: [TimedHighlightedTextTag] = []
        var highlightedLastWordEnd: TimeInterval?
        var highlightingIsPastEndOfLyrics: Bool = true
        var highlightingStyle: LyricsHighlightStyle

        // For Edit View
        var liveTextValue: String = ""
        var isGenerateEnabled: Bool = false
        var replaceText: String?
        var hasReplacedLyrics: Bool {
            return replaceText != nil
        }

        var isWaveformBlockedByWarning: Bool = false

        var buttonText: String {
            if hasReplacedLyrics {
                return L10n.FeatureEditClip.editLyrics
            } else {
                return highlightingIsPastEndOfLyrics ? L10n.FeatureEditClip.addLyrics : L10n.FeatureEditClip.replaceLyrics
            }
        }

        public init(
            lyrics: String,
            lyricsSelectionRange: Range<String.Index>?,
            highlightingStyle: LyricsHighlightStyle,
            highlightedTextTags: [TimedHighlightedTextTag] = []
        ) {
            self.lyrics = lyrics
            self.lyricsSelectionRange = lyricsSelectionRange
            self.replacementLyrics = ""
            self.highlightingStyle = highlightingStyle
            self.highlightedTextTags = highlightedTextTags
        }

        mutating func clearValuesToDefaultWithSameLyrics() {
            self.lyricsSelectionRange = nil
            self.highlightingStyle = .onlyCaptureStart
            self.highlightingIsPastEndOfLyrics = true
        }

        func getTextValueForSelection(_: Bool = false) -> String {
            let aStr = String(lyrics)

            guard
                let range = lyricsSelectionRange,
                highlightingStyle != .noSelection,
                !highlightingIsPastEndOfLyrics
            else { return "" }

            let text = lyrics
            let selectionStyle = highlightingStyle
            let startBound: String.Index
            let endBound: String.Index
            switch selectionStyle {
            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
            }

            let selectRange = startBound ..< endBound

            // Trim the beginning of aStr to newStart
            let selectedString = String(aStr[selectRange])
            return selectedString
        }
    }

    public enum Action: BindableAction {
        public enum Delegate {
            case showReplaceLyricsSelection
            case didToggleInsturmental
            case didUpdateLyrics(String, [TimedHighlightedTextTag])
            case didTapReplaceLyrics
            case didTapClearReplaceLyrics
            case generateLyrics
            case clearIsEditing
        }

        case binding(BindingAction<State>)
        case delegate(Delegate)
        case updateHighlightingStart(TimeInterval)
        case updateLyricsAndTimedTags(String, [TimedHighlightedTextTag])
        case processLyrics([AlignedLyric])
        case updateLiveTextValue

        case setReplaceValue
        case clearReplaceValue
        case clearBlockWarning
        case clearToDefaults
    }

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Reduce<State, Action> { state, action in
            switch action {
            case .updateLyricsAndTimedTags(let lyrics, let tags):
                state.lyrics = lyrics
                state.highlightedTextTags = tags.sorted(by: { $0.startsAt < $1.startsAt })
                state.highlightedLastWordEnd = tags.last?.endsAt
                return .none

            case .updateHighlightingStart(let startTime):
                let lyricsText = state.lyrics
                let tags = state.highlightedTextTags

                if let startCharacter = tags.first(where: { startTime < $0.startsAt }) {
                    state.lyricsSelectionRange = lyricsText.clampedRange(
                        start: startCharacter.startOffset,
                        end: lyricsText.count
                    )
                }
                if let lastWordEndTime = state.highlightedLastWordEnd {
                    state.highlightingIsPastEndOfLyrics = lastWordEndTime < startTime
                } else {
                    state.highlightingIsPastEndOfLyrics = true
                }
                return .none

            case .processLyrics(let alignedWords):
                state.lyricsAlignedWords = alignedWords
                let newLyrics = String(alignedWords.map { $0.word }.joined(by: " "))
                    .replacingOccurrences(of: "\n ", with: "\n")

                var highlightTags: [TimedHighlightedTextTag] = []
                for lyric in alignedWords {
                    let currentLyrics = String(highlightTags.map { $0.word }.joined(by: " "))
                        .replacingOccurrences(of: "\n ", with: "\n")

                    highlightTags.append(
                        .init(
                            startsAt: lyric.startsAt,
                            endsAt: lyric.endsAt,
                            startOffset: currentLyrics.count,
                            word: lyric.word
                        )
                    )
                }
                return .send(.delegate(.didUpdateLyrics(newLyrics, highlightTags)))

            case .updateLiveTextValue:
                if let replaceText = state.replaceText {
                    state.liveTextValue = replaceText
                } else {
                    state.liveTextValue = state.getTextValueForSelection()
                }
                return .none

            case .setReplaceValue:
                state.isWaveformBlockedByWarning = true
                state.replaceText = state.liveTextValue
                return .none

            case .clearBlockWarning:
                state.isWaveformBlockedByWarning = false
                return .none

            case .clearReplaceValue:
                state.replaceText = nil
                state.isWaveformBlockedByWarning = false
                return .concatenate(
                    .send(.updateLiveTextValue),
                    .send(.delegate(.didTapClearReplaceLyrics))
                )

            case .clearToDefaults:
                state.clearValuesToDefaultWithSameLyrics()
                return .none

            case .binding,
                 .delegate:
                return .none
            }
        }
    }
}

public struct LyricsSelectionView: View {
    @Bindable var store: StoreOf<LyricsSelection>
    @Binding var isInstrumental: Bool
    @State private var lyricsCharCountLimit: Int

    public init(
        store: StoreOf<LyricsSelection>,
        isInstrumental: Binding<Bool>,
        lyricsCharCountLimit: Int = 2000
    ) {
        self.store = store
        self._isInstrumental = isInstrumental
        self.lyricsCharCountLimit = lyricsCharCountLimit
    }

    public var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Text(L10n.FeatureCreateClip.lyrics)
                    .typographyV1(.headline4)
                    .foregroundStyle(Color.SemanticV1.textPrimary)

                Text(tagText)
                    .typographyV1(.caption4)
                    .foregroundStyle(.white)
                    .padding(.horizontal, 8.0)
                    .padding(.vertical, 4.0)
                    .background {
                        Color.SemanticV1.textLink
                    }
                    .cornerRadius(8.0)
                    .opacity(tagOpacity)
                    .animation(.easeInOut(duration: 0.2), value: store.highlightingIsPastEndOfLyrics)

                Spacer()

                HStack(spacing: 8) {
                    Text(L10n.FeatureCreateClip.instrumental)
                        .typographyV1(.body1)
                        .foregroundStyle(Color.SemanticV1.textPrimary)
                    Toggle(isOn: $isInstrumental) {}
                        .scaleEffect(0.7)
                        .labelsHidden()
                        .tint(.SemanticV1.iconLink)
                }
                .contentShape(.rect)
                .onTapGesture {
                    store.send(.delegate(.didToggleInsturmental))
                }
            }

            if !isInstrumental {
                ZStack(alignment: .leading) {
                    TimedHighlightedText(
                        text: $store.lyrics.limit(lyricsCharCountLimit),
                        highlightRange: $store.lyricsSelectionRange,
                        highlightingIsPastEndOfLyrics: $store.highlightingIsPastEndOfLyrics,
                        replaceText: $store.replaceText,
                        highlightColor: Color.SemanticV1.textLink,
                        highlightStyle: store.highlightingStyle
                    )
                }
                .typographyV1(.body2.neueMontrealMedium())
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .padding(EdgeInsets(top: .zero, leading: 16, bottom: 16, trailing: 16))
                .background(Color.SemanticV1.backgroundSecondary)
                .cornerRadius(8)
                .opacity(isInstrumental ? 0.5 : 1)
                .disabled(isInstrumental)
            }

            HStack {
                LyricsEditorBigButton(textLabel: store.buttonText) {
                    store.send(.updateLiveTextValue)
                    store.send(.delegate(.didTapReplaceLyrics))
                }

                if store.hasReplacedLyrics {
                    resetButton
                }
            }
        }
    }

    var tagOpacity: CGFloat {
        if let _ = store.replaceText {
            return 1.0
        } else {
            return store.highlightingIsPastEndOfLyrics ? 0.0 : 1.0
        }
    }

    var tagText: String {
        if store.hasReplacedLyrics {
            return store.highlightingIsPastEndOfLyrics ? L10n.FeatureEditClip.added : L10n.FeatureEditClip.replaced
        } else {
            return L10n.FeatureEditClip.selection
        }
    }

    @ViewBuilder
    var resetButton: some View {
        Button {
            store.send(.clearReplaceValue)
        } label: {
            Image.Icon.restart
                .resizable()
                .frame(width: 32.0, height: 32.0)
                .padding(.all, 8.0)
                .padding(.horizontal, 6.0)
                .background {
                    Color.SemanticV1.backgroundQuaternary
                }
                .clipShape(.rect(cornerRadius: .infinity))
        }
        .buttonStyle(.plain)
    }
}

public struct LyricsEditorView: View {
    @Bindable var store: StoreOf<LyricsSelection>

    public init(
        store: StoreOf<LyricsSelection>
    ) {
        self.store = store
    }

    public var body: some View {
        ZStack {
            Color.clear

            VStack {
                HStack {
                    Text(L10n.FeatureEditClip.changeLyrics)
                        .typographyV1(.headline4)
                }
                .padding(.top, 12.0)

                Divider()

                TextEditor(text: $store.liveTextValue)
                    .typographyV1(.body1)
                    .foregroundStyle(Color.SemanticV1.textPrimary)
                    .scrollContentBackground(.hidden)

                HStack {
                    if store.isGenerateEnabled {
                        LyricsEditorBigButton(
                            textLabel: L10n.FeatureEditClip.generateLyrics)
                        {
                            store.send(.delegate(.generateLyrics))
                        }
                    }
                }

                Button {
                    store.send(.delegate(.clearIsEditing))
                    store.send(.setReplaceValue)
                } label: {
                    ZStack {
                        Color.clear
                        Text(L10n.FeatureEditClip.changeLyrics)
                            .foregroundStyle(Color.SemanticV1.textInvert)
                            .typographyV1(.body1)
                    }
                    .frame(height: 64.0)
                    .background {
                        Color.SemanticV1.backgroundInvert
                    }
                    .clipShape(.rect(cornerRadius: 8.0))
                }
                .buttonStyle(.plain)
                .padding(.bottom, 16.0)
            }
            .padding(.horizontal, 8.0)
        }
        .background(Color.SemanticV1.backgroundSecondary)
    }
}
