import APIClient
import ComponentLibrary
import ComposableArchitecture
import Flow
import Localization
import SwiftUI
import UserConfigClient

// MARK: - MusicGenrePicker

@Reducer
public struct MusicGenrePicker {
    static let maxGenresAllowed = 10

    struct SelectableGenre: Equatable, Identifiable {
        let name: String
        var isSelected: Bool

        var id: String { name }

        init(name: String, isSelected: Bool = false) {
            self.name = name
            self.isSelected = isSelected
        }
    }

    // MARK: - State

    @ObservableState
    public struct State: Equatable {
        var me: Me
        var genres: [SelectableGenre] = []
        var rejectedGenreName: String? = nil
        var isWorking = false
        var toggleHapticSuccess = false
        var toggleHapticWarning = false

        init(me: Me) {
            self.me = me
            self.genres = Prompt().genres.map { SelectableGenre(name: $0) }
        }
    }

    // MARK: - Action

    public enum Action {
        case onAppear
        case genreToggled(String)
        case clearWiggle
        case delegate(Delegate)
        case `internal`(Internal)

        @CasePathable
        public enum Delegate: Equatable {
            case onAppear
            case genreSelected(genres: Set<String>, me: Me)
            case maxSelectionAttempted(genreName: String)
            case savedGenresToUserConfig
            case errorSavingGenresToUserConfig
        }

        public enum Internal: Equatable {
            case saveSelection
            case setIsWorking(value: Bool)
        }
    }

    @Dependency(\.continuousClock) var clock
    @Dependency(\.userConfigClient) var userConfigClient

    init() {}

    // MARK: - Reducer Body

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                return .send(.delegate(.onAppear))

            case .genreToggled(let genreName):
                if let index = state.genres.firstIndex(where: { $0.name == genreName }) {
                    let genre = state.genres[index]
                    let currentlySelectedCount = state.genres.filter(\.isSelected).count

                    let shouldRunWiggle = !genre.isSelected && currentlySelectedCount >= Self.maxGenresAllowed
                    if shouldRunWiggle {
                        state.rejectedGenreName = genreName
                        state.toggleHapticWarning.toggle()
                        return .concatenate(
                            .send(.delegate(.maxSelectionAttempted(genreName: genreName))),
                            .run { send in
                                try await clock.sleep(for: .seconds(0.5))
                                await send(.clearWiggle)
                            }
                        )
                    }

                    state.genres[index].isSelected.toggle()
                    state.toggleHapticSuccess.toggle()
                }
                return .none

            case .clearWiggle:
                state.rejectedGenreName = nil
                return .none

            case .internal(let internalAction):
                return handleInternalAction(internalAction, state: &state)

            case .delegate:
                return .none
            }
        }
    }
}

// MARK: - Private Handlers

private extension MusicGenrePicker {
    func handleInternalAction(
        _ internalAction: Action.Internal,
        state: inout State
    ) -> Effect<Action> {
        switch internalAction {
        case .setIsWorking(value: let value):
            state.isWorking = value
            return .none

        case .saveSelection:
            let preferredTags = state.genres
                .filter(\.isSelected)
                .map(\.name)
                .prefix(Self.maxGenresAllowed)
            let request = UserConfigSchema(preferredTags: Array(preferredTags))

            return .concatenate(
                .send(.internal(.setIsWorking(value: true))),
                .run { send in
                    let result = await userConfigClient.update(request)
                    let action: Action = switch result {
                    case .success: .delegate(.savedGenresToUserConfig)
                    case .failure: .delegate(.errorSavingGenresToUserConfig)
                    }
                    await send(action)
                },
                .send(.internal(.setIsWorking(value: false)))
            )
        }
    }
}

// MARK: - MusicGenrePickerScreen

struct MusicGenrePickerScreen: View {
    @Bindable var store: StoreOf<MusicGenrePicker>
    @State private var wiggleOffset: Double = 0

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

    var body: some View {
        MadlibsLayoutView(title: L10n.FeatureOnboarding.madlibsGenresQuestion) {
            ScrollView(.vertical, showsIndicators: false) {
                genreTagsView
                    .padding(.vertical, 20) // Add padding to ensure content isn't cut off
            }
            .mask(
                VStack(spacing: 0) {
                    // Top gradient fade
                    LinearGradient(
                        gradient: Gradient(stops: [
                            .init(color: Color.clear, location: 0),
                            .init(color: Color.black, location: 1.0),
                        ]),
                        startPoint: .top,
                        endPoint: .bottom
                    )
                    .frame(height: 30)

                    // Middle section - fully visible
                    Rectangle()
                        .fill(Color.black)

                    // Bottom gradient fade
                    LinearGradient(
                        gradient: Gradient(stops: [
                            .init(color: Color.black, location: 0),
                            .init(color: Color.clear, location: 1.0),
                        ]),
                        startPoint: .top,
                        endPoint: .bottom
                    )
                    .frame(height: 30)
                }
            )
        }
        .sensoryFeedbackIfEnabled(.impact(weight: .light), trigger: store.toggleHapticSuccess)
        .sensoryFeedbackIfEnabled(.warning, trigger: store.toggleHapticWarning)
    }

    private var genreTagsView: some View {
        HFlow {
            ForEach(store.genres) { genre in
                genreButton(for: genre)
            }
        }
    }

    private func genreButton(
        for genre: MusicGenrePicker.SelectableGenre
    ) -> some View {
        Button(action: { store.send(.genreToggled(genre.name)) }) {
            Text(genre.name)
                .typographyV1(.heading4)
                .foregroundStyle(Color.SemanticV1.alwaysLighterBeige)
                .padding(.horizontal, 16)
                .padding(.vertical, 8)
                .background(genreBackground(genre))
                .clipShape(.capsule)
        }
        .buttonStyle(ScaleButtonStyle())
        .accessibilityLabel(genre.name)
        .accessibilityAddTraits(genre.isSelected ? [.isSelected] : [])
        .rotationEffect(Angle(degrees: store.rejectedGenreName == genre.name ? wiggleOffset : 0))
        .onChange(of: store.rejectedGenreName) { _, newValue in
            if newValue == genre.name {
                startWiggleAnimation()
            }
        }
    }

    @ViewBuilder
    private func genreBackground(
        _ genre: MusicGenrePicker.SelectableGenre
    ) -> some View {
        if genre.isSelected {
            AuraShaderView(
                id: "\(genre.name)-background",
                appPreset: .pinkYellowOrange,
                morphSpeed: 0.005,
                scale: 0.33,
                isPaused: false
            )
        } else {
            Color.SemanticV2.backgroundFogThin
        }
    }

    private func startWiggleAnimation() {
        withAnimation(.linear(duration: 0.05).repeatCount(4, autoreverses: true)) {
            wiggleOffset = 3.0
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            wiggleOffset = 0
        }
    }
}
