import APIClient
import AVFoundation
import ComposableArchitecture
import Localization
import SnippetPlayerClient
import SwiftUI

@Reducer
public struct CustomCreatePlayer {
    @ObservableState
    public struct State: Equatable {
        let clip: Clip
        var readyToPlay: Bool = false
        var isPlaying: Bool = false
        var isScrubbing: Bool = false
        var elapsedTime: Double = 0
        var canSwitchModes: Bool
        var waveformData: [Float] = []

        let formatter: DateComponentsFormatter = {
            var formatter = DateComponentsFormatter()
            formatter.allowedUnits = [.minute, .second]
            formatter.unitsStyle = .positional
            formatter.zeroFormattingBehavior = .pad
            return formatter
        }()

        // TODO: (JY) - We need to be wary of 0/invalid duration clips and handle them properly.
//        let fallbackTotalDuration: Double = 120
//        var totalDuration: Double {
//            clip.duration.
//        }

        public init(clip: Clip, canSwitchModes: Bool = true) {
            self.clip = clip
            self.canSwitchModes = canSwitchModes
        }
    }

    public enum Action: BindableAction {
        case playTapped
        case pauseTapped
        case playbackScrubbing(ScrubbingAction)
        case snippetPlayerEvent(SnippetPlayerClientEvent)
        case seekToStart

        case task
        case binding(BindingAction<State>)
        case switchMode
        case removeClipTapped
        case delegate(Delegate)

        case playerInitialized // TODO: (JY) - Add a loading state here
        case `internal`(Internal)

        public enum Delegate: Equatable {
            case modeSwitchRequested
            case resetToDefaultCustomMode
        }

        public enum ScrubbingAction: Equatable {
            case beginScrubbing(Double) // seconds
            case updateScrubbing(Double) // seconds
            case endScrubbing(Double) // seconds
        }
        
        public enum Internal {
            case loadWaveform
            case waveformLoadResponse(Result<[Float], Error>)
        }
    }

    public init() {}

    @Dependency(\.snippetPlayerClient) private var snippetPlayerClient
    @Dependency(APIClient.self) private var apiClient
    @Dependency(\.continuousClock) private var clock

    public var body: some ReducerOf<Self> {
        Reduce<State, Action> { state, action in
            struct SnippetPlayerEventStreamCancellable: Hashable {}
            struct CheckWaveformCompletionCancellableId: Hashable {}
            switch action {
            case .task:
                let snippet: ClipSnippet = .init(clip: state.clip, startTime: 0, endTime: state.clip.duration)
                return .merge(
                    .stream(
                        snippetPlayerClient.stream(),
                        send: Action.snippetPlayerEvent,
                        cancellableId: SnippetPlayerEventStreamCancellable()
                    ),
                    .run { send in
                        snippetPlayerClient.setup(false)
                        await snippetPlayerClient.loadSnippet(snippet)
                        await send(.playerInitialized)
                    },
                    .send(.internal(.loadWaveform))
                )

            case .playerInitialized:
                state.readyToPlay = true
                return .none

            case .playTapped:
                /// If we're basically at the end, seek to the beginning
                if abs(state.elapsedTime - state.clip.duration) < 0.05 {
                    snippetPlayerClient.seekTo(.zero)
                    state.elapsedTime = 0
                }
                snippetPlayerClient.playCurrentClip()
                return .none

            case .pauseTapped:
                snippetPlayerClient.pauseCurrentClip()
                state.isPlaying = false
                return .none

            case .seekToStart:
                snippetPlayerClient.seekTo(.zero)
                state.elapsedTime = 0
                return .none

            case .snippetPlayerEvent(let event):
                switch event {
                case .snippetChanged:
                    /// Should not be possible
                    /// Should we `assertionFailure` here?
                    return .none

                case .playbackTimeUpdated(let currentTime):
                    if !state.isScrubbing {
                        state.elapsedTime = min(currentTime.seconds, state.clip.duration) // Clamped for display consistency
                    }
                    return .none

                case .playbackStateChanged(let timeControlStatus):
                    state.isPlaying = timeControlStatus == .playing
                    return .none
                }

            case .playbackScrubbing(let scrubbingAction):
                switch scrubbingAction {
                case .beginScrubbing(let seconds):
                    snippetPlayerClient.pauseCurrentClip()
                    state.isScrubbing = true
                    state.elapsedTime = seconds
                    return .none

                case .updateScrubbing(let seconds):
                    state.elapsedTime = seconds
                    return .none

                case .endScrubbing(let seconds):
                    state.elapsedTime = seconds
                    state.isScrubbing = false
                    snippetPlayerClient.seekTo(CMTime(seconds: seconds, preferredTimescale: 1000))
                    snippetPlayerClient.playCurrentClip()
                    return .none
                }

            case .switchMode:
                guard state.canSwitchModes else { return .none }
                return .send(.delegate(.modeSwitchRequested))
                
            case .removeClipTapped:
                return .send(.delegate(.resetToDefaultCustomMode))
                
            case .internal(.loadWaveform):
                return .run { [clip = state.clip] send in
                    do {
                        let alignedLyrics = try await apiClient.getAlignedLyrics(clip)
                        let waveformData = alignedLyrics.waveformData
                        guard !waveformData.isEmpty else {
                            // Poll every 3 seconds if waveform data is not ready
                            return await withTaskCancellation(id: CheckWaveformCompletionCancellableId(), cancelInFlight: true) {
                                for await _ in clock.timer(interval: .seconds(3)) {
                                    await send(.internal(.loadWaveform))
                                }
                            }
                        }
                        await send(.internal(.waveformLoadResponse(.success(waveformData))))
                    } catch {
                        await send(.internal(.waveformLoadResponse(.failure(error))))
                    }
                }
                
            case .internal(.waveformLoadResponse(let result)):
                switch result {
                case .success(let waveform):
                    state.waveformData = waveform
                    return .none
                case .failure:
                    return .none
                }
                
            case .delegate:
                return .none
                
            case .binding:
                return .none
            }
        }
    }
}

public enum CustomCreatePlayerType {
    case cover
    case extend(extendTimestamp: Double, clipDuration: Double, onExtendTimestampChanged: ((Double) -> Void)?)
    
    var isExtendMode: Bool {
        switch self {
        case .cover:
            return false
        case .extend:
            return true
        }
    }
    
    var modeTitle: String {
        switch self {
        case .cover:
            return L10n.FeatureCreateClip.cover
        case .extend:
            return L10n.FeatureCreateClip.extend
        }
    }
}

public struct CustomCreatePlayerView: View {
    @Bindable private var store: StoreOf<CustomCreatePlayer>
    @State private var hasSentScrubStart: Bool = false
    let sectionType: CustomCreatePlayerType

    public init(store: StoreOf<CustomCreatePlayer>, sectionType: CustomCreatePlayerType) {
        self.store = store
        self.sectionType = sectionType
    }
    
    private var showScrubber: Bool {
        switch sectionType {
        case .cover:
            return true
        case .extend:
            return false
        }
    }
    
    private var extendTimestamp: Double? {
        switch sectionType {
        case .cover:
            return nil
        case .extend(let timestamp, _, _):
            return timestamp
        }
    }
    
    private var clipDuration: Double {
        switch sectionType {
        case .cover:
            return store.clip.duration
        case .extend(_, let duration, _):
            return duration
        }
    }
    
    private var onExtendTimestampChanged: ((Double) -> Void)? {
        switch sectionType {
        case .cover:
            return nil
        case .extend(_, _, let callback):
            return callback
        }
    }

    public var body: some View {
        VStack(spacing: 16) {
            HStack(spacing: 8) {
                Button {
                    if store.isPlaying {
                        store.send(.pauseTapped)
                    } else {
                        store.send(.playTapped)
                    }
                } label: {
                    // Album art with play/pause icon on top
                    // Placeholder used to hold the horizontal space
                    ZStack {
                        RoundedRectangle(cornerRadius: 12)
                            .fill(Color.black.opacity(0.5))
                            .frame(width: 40, height: 40)

                        RemoteImage(url: store.clip.largeImageUrl, fallbackId: store.clip.id.remoteId)
                            .aspectRatio(contentMode: .fill)
                            .frame(width: 40, height: 40)/// Dark opacity overlay to help with button contrast
                            .overlay(Color.black.opacity(0.25)) 
                            .clipShape(RoundedRectangle(cornerRadius: 12))

                        (store.isPlaying ? Image.FigmaMCP.pause : Image.FigmaMCP.play)
                            .figmaMCPIconStyle(size: 12, semanticColor: Color.FigmaMCP.Semantic.foregroundPrimary)
                    }
                }
                .buttonStyle(PlainButtonStyle())

                Text(store.clip.title)
                    .figmaMCPTypography(TypographyV1.FigmaMCP.smallTitle)
                    .foregroundStyle(Color.FigmaMCP.Semantic.foregroundPrimary)
                    .lineLimit(1)

                Spacer()
                
                if store.canSwitchModes {
                    HStack(spacing: 8) {
                        Menu {
                            Button {
                                if sectionType.isExtendMode {
                                    store.send(.switchMode)
                                }
                            } label: {
                                HStack {
                                    Text(L10n.FeatureCreateClip.cover)
                                    if !sectionType.isExtendMode {
                                        Image.FigmaMCP.success
                                            .figmaMCPIconStyle(size: 16, semanticColor: Color.FigmaMCP.Semantic.accentBrand)
                                    }
                                }
                            }
                            .disabled(!sectionType.isExtendMode)
                            
                            Button {
                                if !sectionType.isExtendMode {
                                    store.send(.switchMode)
                                }
                            } label: {
                                HStack {
                                    Text(L10n.FeatureCreateClip.extend)
                                    if sectionType.isExtendMode {
                                        Image.FigmaMCP.success
                                            .figmaMCPIconStyle(size: 16, semanticColor: Color.FigmaMCP.Semantic.accentBrand)
                                    }
                                }
                            }
                            .disabled(sectionType.isExtendMode)
                        } label: {
                            HStack(spacing: 4) {
                                Text(sectionType.modeTitle)
                                    .figmaMCPTypography(TypographyV1.FigmaMCP.xSmallTitle)
                                    .foregroundStyle(Color.FigmaMCP.Semantic.foregroundPrimary)
                                    .tracking(0.24)
                                
                                Image.FigmaMCP.chevronDown
                                    .figmaMCPIconStyle(size: 16, semanticColor: Color.FigmaMCP.Semantic.foregroundPrimary)
                            }
                            .padding(.horizontal, 16)
                            .frame(height: 40)
                            .background(Color.FigmaMCP.Semantic.fogThin)
                            .clipShape(RoundedRectangle(cornerRadius: 100))
                        }
                        .buttonStyle(PlainButtonStyle())
                        .fixedSize()
                        
                        Button {
                            store.send(.removeClipTapped)
                        } label: {
                            Image.FigmaMCP.close
                                .resizable()
                                .figmaMCPIconStyle(size: 13.5, semanticColor: Color.FigmaMCP.Semantic.foregroundSecondary)
                        }
                        .frame(width: 24, height: 24)
                        .buttonStyle(PlainButtonStyle())
                    }
                }
            }

            ZStack {
                if case .cover = sectionType {
                    scrubberComponent
                        .id("cover-content")
                        .transition(.opacity)
                } else {
                    extendWaveformComponent
                        .id("extend-content")
                        .transition(.opacity)
                }
            }
            .animation(.easeInOut(duration: 0.25), value: sectionType.isExtendMode)
        }
        .padding(16)
        .background(Color.FigmaMCP.Semantic.fogThin)
        .cornerRadius(16)
        .task { store.send(.task) }
    }
    
    private var scrubberComponent: some View {
        CoverModeScrubber(store: store, hasSentScrubStart: $hasSentScrubStart)
    }
    
    private var extendWaveformComponent: some View {
        ExtendModeWaveform(
            store: store,
            extendTimestamp: extendTimestamp ?? 0,
            clipDuration: clipDuration,
            onExtendTimestampChanged: onExtendTimestampChanged
        )
    }
}
