import ComponentLibrary
import ComposableArchitecture
import Localization
import SwiftUI

@Reducer
public struct ToastReducer {
    let dismissDelay: Duration = .seconds(4)

    public enum Action {
        // Internal use only
        case setToast(State.ToastType?)

        case show(State.ToastType)
        case dismiss
        case undo
        case setConnected(Bool)
    }

    @ObservableState
    public struct State: Equatable {
        public struct ToastType: Equatable {
            public enum Destination: Equatable {
                case editCaption(String)
                case newClips
                case paywall
            }

            public enum TrailingAccessory: Equatable {
                case dismiss
                case text(String)
                case undo
            }

            public var id: UUID = UUID()
            public var title: String?
            public var message: Toast.Message
            public var style: Toast.Style
            public var position: Toast.Position = .top
            public var destination: Destination?
            public var trailingAccessory: TrailingAccessory?
            public var autoDismiss: Bool = true
            public var disableDismiss: Bool = false

            var paddingEdge: Edge.Set {
                switch position {
                case .top: return .top
                case .bottom: return .bottom
                }
            }

            var paddingVertical: CGFloat {
                switch position {
                case .top: return 16
                case .bottom: return 8
                }
            }

            var transitionEdge: Edge {
                switch position {
                case .top: return .top
                case .bottom: return .bottom
                }
            }

            var displayAsButton: Bool {
                return trailingAccessory != .undo || trailingAccessory != .dismiss
            }

            public static func warning(_ title: String?, _ message: Toast.Message = .string(""), position: Toast.Position = .top, destination: Destination? = nil, trailingView: TrailingAccessory? = nil) -> ToastType {
                .init(title: title, message: message, style: .warning, position: position, destination: destination, trailingAccessory: trailingView)
            }

            public static func success(_ title: String, _ message: Toast.Message = .string(""), position: Toast.Position = .top, destination: Destination? = nil, trailingView: TrailingAccessory? = nil) -> ToastType {
                .init(title: title, message: message, style: .success, position: position, destination: destination, trailingAccessory: trailingView)
            }

            public static func information(_ title: String, _ message: Toast.Message = .string(""), position: Toast.Position = .top, destination: Destination? = nil, trailingView: TrailingAccessory? = nil) -> ToastType {
                .init(title: title, message: message, style: .information, position: position, destination: destination, trailingAccessory: trailingView)
            }

            public static func disableCreate(_ message: Toast.Message) -> ToastType {
                .init(message: message, style: .snooze, position: .bottom)
            }

            public static func connected() -> ToastType {
                .init(title: nil, message: .string(L10n.FeatureToasts.online), style: .connected)
            }

            public static func disconnected() -> ToastType {
                .init(title: nil, message: .string(L10n.FeatureToasts.offline), style: .disconnected)
            }

            public static func preparingShare() -> ToastType {
                .init(title: L10n.FeatureToasts.preparing, message: .string(""), style: .preparing, position: .bottom, destination: nil, autoDismiss: false, disableDismiss: true)
            }

            public static func generationComplete() -> ToastType {
                .init(title: nil, message: .string(L10n.FeatureToasts.songsReady), style: .generationComplete, position: .top, destination: .newClips, autoDismiss: false)
            }

            public static func successV2(_ title: String, _ message: Toast.Message = .string(""), image: RemoteImage?, position: Toast.Position = .top, destination: Destination? = nil, trailingView: TrailingAccessory? = nil) -> ToastType {
                .init(title: title, message: message, style: .successV2(image), position: position, destination: destination, trailingAccessory: trailingView)
            }

            public static func warningV2(_ title: String, _ message: Toast.Message = .string(""), image: RemoteImage?, position: Toast.Position = .top, destination: Destination? = nil, trailingView: TrailingAccessory? = nil, autoDismiss: Bool = true) -> ToastType {
                .init(title: title, message: message, style: .warningV2(image), position: position, destination: destination, trailingAccessory: trailingView, autoDismiss: autoDismiss)
            }

            public static func captionPrompt(clipId: String) -> ToastType {
                .init(
                    title: L10n.FeatureCaptions.addCaptionToastTitle,
                    message: .string(""),
                    style: .caption,
                    position: .top,
                    destination: .editCaption(clipId),
                    trailingAccessory: .text(L10n.FeatureCaptions.addCaptionToastLink),
                    autoDismiss: true
                )
            }
        }

        public var toast: ToastType?

        public init() {}
    }

    public init() {}

    @Dependency(\.toastClient.undo) var undo

    public var body: some Reducer<State, Action> {
        Reduce { state, action in
            struct ToastDismissCancellableID: Hashable {}
            struct ToastAppearCancellableID: Hashable {}

            switch action {
            case .setToast(let toast):
                state.toast = toast
                return .none

            case .show(let type):
                var actions: [Effect<ToastReducer.Action>] = [
                    // Dismiss previous toasts
                    .send(.setToast(nil)),
                    .cancel(id: ToastDismissCancellableID()),
                    .cancel(id: ToastAppearCancellableID()),
                    // Show toast
                    .run { [type = type] send in
                        try await Task.sleep(for: .seconds(0.3))
                        await send(.setToast(type), animation: .default)
                    }.cancellable(id: ToastAppearCancellableID()),
                ]
                if type.autoDismiss {
                    actions.append(
                        // Auto hide
                        .run { send in
                            try await Task.sleep(for: dismissDelay)
                            await send(.dismiss, animation: .default)
                        }.cancellable(id: ToastDismissCancellableID())
                    )
                }
                return .concatenate(actions)

            case .dismiss:
                return .send(.setToast(nil), animation: .default)

            case .undo:
                undo()
                return .send(.setToast(nil), animation: .default)

            case .setConnected(let isConnected):
                if isConnected {
                    guard state.toast == .disconnected() else { return .none }

                    return .merge(
                        .cancel(id: ToastDismissCancellableID()),
                        .send(.setToast(.connected())),
                        .run { send in
                            try await Task.sleep(for: .seconds(2))
                            await send(.dismiss)
                        }
                        .cancellable(id: ToastDismissCancellableID())
                    )
                } else {
                    return .merge(
                        .cancel(id: ToastDismissCancellableID()),
                        .send(.setToast(.disconnected()))
                    )
                }
            }
        }
    }
}

public struct ToastView: View {
    let store: StoreOf<ToastReducer>
    let action: ((ToastReducer.State.ToastType.Destination?) -> Void)?
    let undoAction: (() -> Void)?

    public init(store: StoreOf<ToastReducer>, action: ((ToastReducer.State.ToastType.Destination?) -> Void)? = nil, undoAction: (() -> Void)? = nil) {
        self.store = store
        self.action = action
        self.undoAction = undoAction
    }

    public var body: some View {
        ZStack {
            switch store.toast {
            case .some(let toast) where toast.style == .generationComplete:
                GenerationCompleteView(toast: toast, tapAction: { action?(.newClips) })
                    .transition(.move(edge: toast.transitionEdge).combined(with: .opacity))
                    .padding(.horizontal, 8)
                    .padding(toast.paddingEdge, toast.paddingVertical)

            case .some(let toast):
                Toast.ComponentView(
                    title: toast.title,
                    message: toast.message,
                    style: toast.style,
                    position: toast.position,
                    displayAsButton: toast.displayAsButton,
                    tapAction: {
                        guard !toast.disableDismiss else {
                            return
                        }
                        if let action {
                            action(toast.destination)
                        }
                        store.send(.setToast(nil), animation: .default)
                    },
                    trailingView: trailingView
                )
                .transition(.move(edge: toast.transitionEdge).combined(with: .opacity))
                .padding(.horizontal, 8)
                .padding(toast.paddingEdge, toast.paddingVertical)
                .allowsHitTesting(!toast.disableDismiss)

            case .none:
                EmptyView()
            }
        }
        .animation(.spring(), value: store.toast)
    }

    @ViewBuilder
    private var trailingView: some View {
        switch store.toast?.trailingAccessory {
        case let .text(label):
            Text(label)
                .typographyV1(.caption5)
                .foregroundStyle(Color.SemanticV1.textLink)

        case .undo:
            Toast.UndoButton {
                undoAction?()
                store.send(.undo)
            }

        case .dismiss:
            Toast.DismissButton {
                store.send(.setToast(nil), animation: .default)
            }

        case nil:
            EmptyView()
        }
    }
}
