import APIClient
import ComposableArchitecture
import EventBusClient
import FeatureToasts
import Foundation
import GenAPI
import Localization
import UIKit
import Utilities

private let log = Logger(category: "HooksPostClient")

public enum HooksPostError: LocalizedError, Equatable {
    case hookCreationFailed(String)
    case missingUploadData
    case videoUploadTimeout
    case uploadFailed
    case streamContinuationFailed
    case taskNotFound
    case noCompletedUploadFound
    case uploadStreamEndedUnexpectedly
    case hookStatusError

    public var errorDescription: String? {
        switch self {
        case .hookCreationFailed(let message):
            return "Hook creation failed: \(message)"
        case .missingUploadData:
            return "Missing required upload data"
        case .videoUploadTimeout:
            return "Video upload timed out"
        case .uploadFailed:
            return "Video upload failed"
        case .streamContinuationFailed:
            return "Stream continuation failed"
        case .taskNotFound:
            return "Task not found"
        case .noCompletedUploadFound:
            return "No completed upload found"
        case .uploadStreamEndedUnexpectedly:
            return "Upload stream ended unexpectedly"
        case .hookStatusError:
            return "Hook status error"
        }
    }
}

public enum HooksPostClientStatus: Equatable {
    case queued
    case uploadingVideo
    case uploadVideoSuccessful
    case uploadVideoFailed
    case creatingHook
    case createHookSuccessful
    case createHookFailed
    case awaitingHookReadiness
    case hookProcessingFailed // Network error / timeout while polling
    case hookFailedModeration // Server says hook failed moderation
    case hookReady
}

public struct HooksPostClientTask: Equatable, Identifiable {
    public let id: String
    public let type: TaskType
    public var status: HooksPostClientStatus
    public var uploadId: String?
    public var s3Id: String?
    public var thumbnailImage: UIImage?
    public let startTime: Date

    public enum TaskType: Equatable {
        case uploadVideo(videoURL: URL)
        case createHook(clipId: String, clipStartTime: Double, clipEndTime: Double, sourceVideoStartTime: Double, sourceVideoEndTime: Double, caption: String, videoVolumeLevel: Float, snippetVolumeLevel: Float, showLyrics: Bool, allowComments: Bool)
        case awaitHookReadiness(hookId: String)

        var processingStatus: HooksPostClientStatus {
            switch self {
            case .uploadVideo:
                return .uploadingVideo
            case .createHook:
                return .creatingHook
            case .awaitHookReadiness:
                return .awaitingHookReadiness
            }
        }
    }

    public init(
        id: String = UUID().uuidString,
        type: TaskType,
        status: HooksPostClientStatus,
        thumbnailImage: UIImage? = nil
    ) {
        self.id = id
        self.type = type
        self.status = status
        self.startTime = Date()
        self.thumbnailImage = thumbnailImage
    }
}

@DependencyClient
public struct HooksPostClient {
    public enum HooksPostClientEvent: Equatable {
        case statusChanged(taskId: String, status: HooksPostClientStatus, thumbnailImage: UIImage? = nil, videoUploadId: String? = nil)
        case uploadComplete(taskId: String, uploadId: String, s3Id: String)
        case cancelled(taskId: String)
    }

    public var uploadVideo: (URL, UIImage?) async throws -> Void = { _, _ in }
    public var createHook: (
        _ clipId: String,
        _ clipStartTime: Double,
        _ clipEndTime: Double,
        _ sourceVideoStartTime: Double,
        _ sourceVideoEndTime: Double,
        _ caption: String,
        _ thumbnailImage: UIImage?,
        _ videoVolumeLevel: Float,
        _ snippetVolumeLevel: Float,
        _ showLyrics: Bool,
        _ allowComments: Bool
    ) async throws -> Void = { _, _, _, _, _, _, _, _, _, _, _ in }
    public var cancelHookPost: () async throws -> Void = {}
    public var events: () -> AsyncStream<HooksPostClientEvent> = { .never }
    public var cancelAllTasks: () async -> Void = {}
    public var resetWithErrors: () async -> Void = {}
    public var fetchMostRecentTask: () async -> HooksPostClientTask? = { nil }
}

private actor HooksPostState {
    private var tasksDictionary: [String: HooksPostClientTask] = [:]
    private var tasksOrder: [String] = []
    private var isProcessing = false
    private var eventContinuations: [UUID: AsyncStream<HooksPostClient.HooksPostClientEvent>.Continuation] = [:]
    private var isCancelled = false
    private var isCancellationInProgress = false
    private var queueProcessorTask: Task<Void, Never>?

    // AsyncStream for task queue
    private var taskStreamContinuation: AsyncStream<HooksPostClientTask>.Continuation?
    private var taskStream: AsyncStream<HooksPostClientTask>

    init() {
        let (stream, continuation) = AsyncStream<HooksPostClientTask>.makeStream()
        self.taskStream = stream
        self.taskStreamContinuation = continuation
    }

    func appendTask(_ task: HooksPostClientTask) throws {
        // Don't accept new tasks during cancellation
        guard !isCancellationInProgress else {
            return
        }

        guard tasksDictionary[task.id] == nil else {
            assertionFailure("Duplicate task ID: \(task.id)")
            return
        }

        // Stream continuation must exist - throw if it doesn't
        guard let continuation = taskStreamContinuation else {
            log.telemetry.error(
                HooksPostError.streamContinuationFailed,
                message: "HooksPostClient: stream continuation failed - error: Stream continuation is nil"
            )
            throw HooksPostError.streamContinuationFailed
        }

        tasksDictionary[task.id] = task
        tasksOrder.append(task.id)
        continuation.yield(task)
    }

    func updateTask(_ task: HooksPostClientTask) {
        tasksDictionary[task.id] = task
    }

    // Retrieves the most recently completed video upload task to enable sequential hook creation.
    func getLatestCompletedUploadTask() -> HooksPostClientTask? {
        for taskId in tasksOrder.reversed() {
            if let task = tasksDictionary[taskId],
               case .uploadVideo = task.type,
               task.status == .uploadVideoSuccessful,
               task.uploadId != nil,
               task.s3Id != nil
            {
                return task
            }
        }

        // Log diagnostic info if no upload found
        let uploadTasks = self.tasksOrder.compactMap { taskId -> String? in
            guard let task = self.tasksDictionary[taskId], case .uploadVideo = task.type else { return nil }
            return "status: \(task.status), uploadId: \(task.uploadId ?? "nil"), s3Id: \(task.s3Id ?? "nil")"
        }
        log.telemetry.error(HooksPostError.noCompletedUploadFound, message: "HooksPostClient: noCompletedUploadFound - queueSize: \(self.tasksOrder.count), uploadTasksInQueue: [\(uploadTasks.joined(separator: "; "))]")
        return nil
    }

    func getAllTasksSortedByDate() -> HooksPostClientTask? {
        // Return the most recent task by startTime
        return tasksDictionary.values.max { $0.startTime < $1.startTime }
    }

    func updateTaskUploadId(id: String, uploadId: String?) {
        if var task = tasksDictionary[id] {
            task.uploadId = uploadId
            tasksDictionary[id] = task
        } else {
            log.telemetry.assertionFailure("HooksPostClient: updateTaskUploadId failed, error: Task not found")
        }
    }

    func setProcessing(_ processing: Bool) {
        isProcessing = processing
    }

    func isCurrentlyProcessing() -> Bool {
        return isProcessing
    }

    func updateTask(id: String, status: HooksPostClientStatus) {
        if var task = tasksDictionary[id] {
            let oldStatus = task.status
            task.status = status
            tasksDictionary[id] = task
        } else {
            log.telemetry.error(HooksPostError.taskNotFound, message: "HooksPostClient: updateTask failed - targetStatus: \(String(describing: status))")
        }
    }

    func updateTaskUploadData(id: String, uploadId: String, s3Id: String) {
        if var task = tasksDictionary[id] {
            task.uploadId = uploadId
            task.s3Id = s3Id
            tasksDictionary[id] = task
        } else {
            log.telemetry.error(HooksPostError.taskNotFound, message: "HooksPostClient: updateTaskUploadData failed - uploadId: \(uploadId), s3Id: \(s3Id)")
        }
    }

    func addEventContinuation(id: UUID, continuation: AsyncStream<HooksPostClient.HooksPostClientEvent>.Continuation) {
        eventContinuations[id] = continuation
    }

    func removeEventContinuation(id: UUID) {
        eventContinuations[id] = nil
    }

    func broadcastEvent(_ event: HooksPostClient.HooksPostClientEvent) {
        for continuation in eventContinuations.values {
            continuation.yield(event)
        }
    }

    func cancelAllTasks() {
        // User-initiated cancellation - don't show error toasts
        reset()
    }

    func resetWithErrors() {
        // System failure - show error toasts
        broadcastErrorsForInProgressTasks()
        reset()
    }

    private func broadcastErrorsForInProgressTasks() {
        for (_, task) in tasksDictionary where task.status == .queued ||
            task.status == .uploadingVideo ||
            task.status == .creatingHook ||
            task.status == .awaitingHookReadiness
        {
            // Use appropriate failure status based on task type
            let failureStatus: HooksPostClientStatus
            switch task.type {
            case .uploadVideo:
                failureStatus = .uploadVideoFailed
            case .createHook:
                failureStatus = .createHookFailed
            case .awaitHookReadiness:
                failureStatus = .hookProcessingFailed
            }

            let statusEvent = HooksPostClient.HooksPostClientEvent.statusChanged(
                taskId: task.id,
                status: failureStatus,
                thumbnailImage: nil,
                videoUploadId: tasksDictionary[task.id]?.uploadId
            )
            for continuation in eventContinuations.values {
                continuation.yield(statusEvent)
            }
        }
    }

    private func reset() {
        isCancelled = true
        queueProcessorTask?.cancel()
        queueProcessorTask = nil

        // Terminate the stream to stop queue processing
        taskStreamContinuation?.finish()
        taskStreamContinuation = nil

        tasksDictionary.removeAll()
        tasksOrder.removeAll()
        isProcessing = false
    }

    func clearAllTasks() {
        let taskCount = tasksDictionary.count
        tasksDictionary.removeAll()
        tasksOrder.removeAll()
    }

    func cleanupCompletedTasks() {
        let hasTerminalState = tasksDictionary.values.contains { task in
            switch task.status {
            case .uploadVideoFailed, .createHookFailed, .hookProcessingFailed, .hookFailedModeration, .hookReady:
                return true
            case .queued, .uploadingVideo, .uploadVideoSuccessful, .creatingHook, .createHookSuccessful, .awaitingHookReadiness:
                return false
            }
        }

        if hasTerminalState {
            clearAllTasks()
            taskStreamContinuation?.finish()
            taskStreamContinuation = nil
            queueProcessorTask?.cancel()
            queueProcessorTask = nil
        }
    }

    func resetCancellation() {
        isCancelled = false
    }

    func checkCancellation() -> Bool {
        return isCancelled
    }

    func getCurrentHookId() -> String? {
        // Find the most recent hook task that's in progress
        for taskId in tasksOrder.reversed() {
            if let task = tasksDictionary[taskId] {
                switch task.type {
                case .awaitHookReadiness(let hookId):
                    // Return hookId from tasks awaiting readiness
                    return hookId
                default:
                    continue
                }
            }
        }
        return nil
    }

    func performCancellationWithHookId() -> (hookId: String?, taskIds: [String]) {
        // Set cancellation flag to prevent race conditions
        isCancellationInProgress = true

        // Get hook ID before cancelling tasks
        let hookId = getCurrentHookId()

        // Get all active task IDs that will be cancelled
        let activeTasks = tasksOrder.compactMap { taskId in
            if let task = tasksDictionary[taskId],
               task.status != .hookReady, task.status != .uploadVideoFailed, task.status != .createHookFailed, task.status != .hookProcessingFailed, task.status != .hookFailedModeration
            {
                return taskId
            }
            return nil
        }

        // Now cancel all tasks
        cancelAllTasks()

        return (hookId, activeTasks)
    }

    func completeCancellation() {
        // Clear the cancellation flag after API call completes
        isCancellationInProgress = false
    }

    func ensureQueueProcessorRunning() -> Task<Void, Never>? {
        // Don't restart queue during cancellation
        guard !isCancellationInProgress else {
            return nil
        }

        if let existingTask = queueProcessorTask, !existingTask.isCancelled {
            return nil
        }

        isCancelled = false

        if taskStreamContinuation == nil {
            let (stream, continuation) = AsyncStream<HooksPostClientTask>.makeStream()
            self.taskStream = stream
            self.taskStreamContinuation = continuation

            for taskId in tasksOrder {
                if let task = tasksDictionary[taskId], task.status == .queued {
                    continuation.yield(task)
                }
            }
        }

        let newTask = Task {
            await processQueueLoop()
        }

        queueProcessorTask = newTask
        return newTask
    }

    func processQueueLoop() async {
        resetCancellation()

        for await task in taskStream {
            // Check if cancelled
            guard !checkCancellation() else {
                break
            }

            guard let currentTask = tasksDictionary[task.id],
                  currentTask.status == .queued
            else {
                continue
            }

            guard tryStartProcessing() else {
                taskStreamContinuation?.yield(task)
                continue
            }

            do {
                let processingStatus = task.type.processingStatus
                updateTask(id: task.id, status: processingStatus)
                try await processTask(task)
            } catch {
                log.telemetry.error(error, message: "HooksPostClient: processTask failed - taskType: \(String(describing: task.type)), underlyingError: \(error.underlyingError ?? "none")")
                if !checkCancellation() {
                    await handleTaskError(taskId: task.id, taskType: task.type, error: error)
                }
            }

            setProcessing(false)
        }

        queueProcessorTask = nil
    }

    private func tryStartProcessing() -> Bool {
        if isProcessing {
            return false
        }
        isProcessing = true
        return true
    }

    func setProcessTaskHandler(_ handler: @escaping (HooksPostClientTask) async throws -> Void) {
        self.processTaskHandler = handler
    }

    private var processTaskHandler: ((HooksPostClientTask) async throws -> Void)?

    private func processTask(_ task: HooksPostClientTask) async throws {
        guard let handler = processTaskHandler else {
            throw HooksPostError.missingUploadData
        }
        try await handler(task)
    }

    private func handleTaskError(taskId: String, taskType: HooksPostClientTask.TaskType, error: Error) async {
        let failureStatus: HooksPostClientStatus
        let errorType: String
        let taskTypeDescription: String

        switch taskType {
        case .uploadVideo(let videoURL):
            failureStatus = .uploadVideoFailed
            errorType = "Video Upload"
            taskTypeDescription = "uploadVideo(url: \(videoURL.lastPathComponent))"

        case .createHook(let clipId, let clipStartTime, let clipEndTime, let sourceVideoStartTime, let sourceVideoEndTime, let caption, let videoVolumeLevel, let snippetVolumeLevel, let showLyrics, let allowComments):
            failureStatus = .createHookFailed
            errorType = "Hook Creation"
            taskTypeDescription = "createHook(clipId: \(clipId), clipRange: \(clipStartTime)-\(clipEndTime), sourceRange: \(sourceVideoStartTime)-\(sourceVideoEndTime), captionLength: \(caption.count), videoVol: \(videoVolumeLevel), snippetVol: \(snippetVolumeLevel), showLyrics: \(showLyrics), allowComments: \(allowComments))"

        case .awaitHookReadiness(let hookId):
            failureStatus = .hookProcessingFailed
            errorType = "Hook Processing"
            taskTypeDescription = "awaitHookReadiness(hookId: \(hookId))"
        }

        let errorMessage = error.underlyingApiError?.errorDetail ?? error.underlyingError ?? "Unknown error"

        let apiErrorCode = (error as NSError).code
        let apiErrorDomain = (error as NSError).domain

        log.telemetry.error(error, message: """
        HooksPostClient: \(errorType.lowercased()) failed - \
        taskType: \(taskTypeDescription), \
        failureStatus: \(String(describing: failureStatus)), \
        errorMessage: \(errorMessage), \
        errorCode: \(apiErrorCode), \
        errorDomain: \(apiErrorDomain)
        """)

        updateTask(id: taskId, status: failureStatus)
        let statusEvent = HooksPostClient.HooksPostClientEvent.statusChanged(
            taskId: taskId,
            status: failureStatus,
            thumbnailImage: nil,
            videoUploadId: tasksDictionary[taskId]?.uploadId
        )
        broadcastEvent(statusEvent)

        cleanupCompletedTasks()
    }
}

extension HooksPostClient: DependencyKey {
    public static var liveValue: HooksPostClient {
        @Dependency(APIClientV2.self) var api
        @Dependency(HooksVideoUploadClient.self) var videoUploadClient
        @Dependency(\.continuousClock) var clock
        @Dependency(\.eventBus.sendHookEvent) var sendHookEvent

        let state = HooksPostState()
        let timeout: TimeInterval = 300

        Task {
            await state.setProcessTaskHandler { task in
                switch task.type {
                case .uploadVideo(let videoURL):
                    log.info("HooksPostClient: uploadVideo processing started - videoURL: \(videoURL.lastPathComponent)")
                    try await processVideoUpload(
                        taskId: task.id,
                        videoURL: videoURL,
                        state: state,
                        videoUploadClient: videoUploadClient,
                        timeout: timeout,
                        thumbnailImage: task.thumbnailImage
                    )
                    log.info("HooksPostClient: uploadVideo processing completed - videoURL: \(videoURL.lastPathComponent)")

                case .createHook(let clipId, let clipStartTime, let clipEndTime,
                                 let sourceVideoStartTime, let sourceVideoEndTime, let caption, let videoVolumeLevel, let snippetVolumeLevel, let showLyrics, let allowComments):
                    log.info("HooksPostClient: createHook processing started - clipId: \(clipId), clipRange: \(clipStartTime)-\(clipEndTime), sourceRange: \(sourceVideoStartTime)-\(sourceVideoEndTime), videoVol: \(videoVolumeLevel), snippetVol: \(snippetVolumeLevel)")

                    let uploadTask = await state.getLatestCompletedUploadTask()
                    guard let uploadTask = uploadTask,
                          let uploadId = uploadTask.uploadId,
                          let s3Id = uploadTask.s3Id
                    else {
                        log.telemetry.error(HooksPostError.missingUploadData, message: "HooksPostClient: createHook failed at upload data retrieval - clipId: \(clipId)")
                        throw HooksPostError.missingUploadData
                    }

                    log.info("HooksPostClient: createHook retrieved upload data - clipId: \(clipId), uploadId: \(uploadId), s3Id: \(s3Id)")

                    let hookId: String
                    do {
                        log.info("HooksPostClient: createHook calling API - clipId: \(clipId), uploadId: \(uploadId), s3Id: \(s3Id)")
                        hookId = try await createVideoHook(
                            clipId: clipId,
                            s3Id: s3Id,
                            uploadId: uploadId,
                            clipStartTime: clipStartTime,
                            clipEndTime: clipEndTime,
                            sourceVideoStartTime: sourceVideoStartTime,
                            sourceVideoEndTime: sourceVideoEndTime,
                            caption: caption,
                            videoVolumeLevel: videoVolumeLevel,
                            snippetVolumeLevel: snippetVolumeLevel,
                            showLyrics: showLyrics,
                            allowComments: allowComments
                        )
                        log.info("HooksPostClient: createHook API succeeded - clipId: \(clipId), hookId: \(hookId)")
                    } catch {
                        log.telemetry.error(error, message: "HooksPostClient: createHook API call failed - clipId: \(clipId), uploadId: \(uploadId), s3Id: \(s3Id)")
                        throw error
                    }

                    await state.updateTask(id: task.id, status: .createHookSuccessful)
                    let statusEvent = HooksPostClient.HooksPostClientEvent.statusChanged(
                        taskId: task.id,
                        status: .createHookSuccessful,
                        thumbnailImage: task.thumbnailImage,
                        videoUploadId: uploadId
                    )
                    await state.broadcastEvent(statusEvent)

                    log.info("HooksPostClient: createHook transitioning to polling - hookId: \(hookId)")
                    try await Self.startAwaitingHookReadiness(
                        hookId: hookId,
                        state: state,
                        clock: clock,
                        thumbnailImage: task.thumbnailImage,
                        videoUploadId: uploadId
                    )
                    log.info("HooksPostClient: createHook polling initiated - hookId: \(hookId)")

                case .awaitHookReadiness(let hookId):
                    log.info("HooksPostClient: awaitHookReadiness processing started - hookId: \(hookId)")
                }
            }
        }

        @Sendable
        func processVideoUpload(
            taskId: String,
            videoURL: URL,
            state: HooksPostState,
            videoUploadClient: HooksVideoUploadClient,
            timeout: TimeInterval,
            thumbnailImage: UIImage?
        ) async throws {
            let statusEvent = HooksPostClientEvent.statusChanged(
                taskId: taskId,
                status: .uploadingVideo,
                thumbnailImage: thumbnailImage,
                videoUploadId: nil
            )
            await state.broadcastEvent(statusEvent)

            var uploadId: String?
            var s3Id: String?

            let startTime = Date()
            var lastProgress: Double = 0.0

            for await event in videoUploadClient.uploadAndProcessVideo(videoURL) {
                let elapsed = Date().timeIntervalSince(startTime)

                if await state.checkCancellation() {
                    throw CancellationError()
                }

                if Date().timeIntervalSince(startTime) > timeout {
                    log.telemetry.error(HooksPostError.videoUploadTimeout, message: "HooksPostClient: uploadTimeout - uploadId: \(uploadId ?? "none"), elapsed: \(elapsed)s, timeout: \(timeout)s, lastProgress: \(Int(lastProgress * 100))%")
                    throw HooksPostError.videoUploadTimeout
                }

                switch event {
                case .startedVideoUpload(let id):
                    uploadId = id
                    let startedEvent = HooksPostClientEvent.statusChanged(
                        taskId: taskId,
                        status: .uploadingVideo,
                        thumbnailImage: thumbnailImage,
                        videoUploadId: id
                    )
                    await state.broadcastEvent(startedEvent)

                case .uploadProgress(let id, let progress):
                    lastProgress = progress

                case .videoUploadComplete(let id, let s3):
                    await state.updateTaskUploadData(id: taskId, uploadId: id, s3Id: s3)
                    await state.updateTask(id: taskId, status: .uploadVideoSuccessful)

                    let completeEvent = HooksPostClientEvent.uploadComplete(
                        taskId: taskId,
                        uploadId: id,
                        s3Id: s3
                    )
                    let statusEventSuccess = HooksPostClientEvent.statusChanged(
                        taskId: taskId,
                        status: .uploadVideoSuccessful,
                        thumbnailImage: thumbnailImage,
                        videoUploadId: id
                    )
                    await state.broadcastEvent(completeEvent)
                    await state.broadcastEvent(statusEventSuccess)
                    return

                case .uploadFailed(let error):
                    let errorDetail = error.underlyingApiError?.errorDetail ?? error.underlyingError ?? "Unknown error"
                    log.telemetry.error(error, message: "HooksPostClient: uploadFailed - uploadId: \(uploadId ?? "none"), elapsed: \(elapsed)s, errorDetail: \(errorDetail)")
                    throw error
                }
            }

            let finalElapsed = Date().timeIntervalSince(startTime)
            let diagnosticInfo = """
            uploadId: \(uploadId ?? "none"), \
            s3Id: \(s3Id ?? "none"), \
            lastProgress: \(Int(lastProgress * 100))%, \
            elapsed: \(finalElapsed)s, \
            timeout: \(timeout)s, \
            reason: Stream ended without completion or failure event
            """
            log.telemetry.error(HooksPostError.uploadStreamEndedUnexpectedly, message: "HooksPostClient: uploadStreamEnded - \(diagnosticInfo)")
            throw HooksPostError.uploadStreamEndedUnexpectedly
        }

        return Self(
            uploadVideo: { videoURL, thumbnailImage in
                let taskId = UUID().uuidString
                let task = HooksPostClientTask(id: taskId, type: .uploadVideo(videoURL: videoURL), status: .queued, thumbnailImage: thumbnailImage)

                // Ensure queue processor is running BEFORE appending task
                _ = await state.ensureQueueProcessorRunning()

                try await state.appendTask(task)
            },

            createHook: { @Sendable clipId, clipStartTime, clipEndTime, sourceVideoStartTime, sourceVideoEndTime, caption, thumbnailImage, videoVolumeLevel, snippetVolumeLevel, showLyrics, allowComments in
                let taskId = UUID().uuidString
                let task = HooksPostClientTask(
                    id: taskId,
                    type: .createHook(
                        clipId: clipId,
                        clipStartTime: clipStartTime,
                        clipEndTime: clipEndTime,
                        sourceVideoStartTime: sourceVideoStartTime,
                        sourceVideoEndTime: sourceVideoEndTime,
                        caption: caption,
                        videoVolumeLevel: videoVolumeLevel,
                        snippetVolumeLevel: snippetVolumeLevel,
                        showLyrics: showLyrics,
                        allowComments: allowComments
                    ),
                    status: .queued,
                    thumbnailImage: thumbnailImage
                )

                // Ensure queue processor is running BEFORE appending task
                _ = await state.ensureQueueProcessorRunning()

                try await state.appendTask(task)

                let statusEvent = HooksPostClientEvent.statusChanged(taskId: taskId, status: .creatingHook, thumbnailImage: thumbnailImage)
                await state.broadcastEvent(statusEvent)
            },

            cancelHookPost: {
                @Dependency(\.toastClient.show) var showToast

                // Immediately cancel the URLSession upload task (synchronous)
                videoUploadClient.cancelCurrentUpload()

                // Use atomic cancellation
                let cancellationResult = await state.performCancellationWithHookId()

                // Emit cancelled events for all active tasks
                for taskId in cancellationResult.taskIds {
                    let cancelledEvent = HooksPostClientEvent.cancelled(taskId: taskId)
                    await state.broadcastEvent(cancelledEvent)
                }

                guard let hookId = cancellationResult.hookId else {
                    await state.completeCancellation()
                    return
                }

                // Ensure cancellation flag is cleared even if API call fails
                defer {
                    Task {
                        await state.completeCancellation()
                    }
                }

                do {
                    _ = try await api.deleteHook(hookId)
                    sendHookEvent(.hookDeleted(hookId: hookId))
                } catch {
                    log.telemetry.error(error, message: "HooksPostClient: deleteHook failed - hookId: \(hookId)")

                    let errorMessage = L10n.FeatureCatalog.actionFailed
                    showToast(.warning(errorMessage))
                }
            },

            events: {
                AsyncStream { continuation in
                    let id = UUID()
                    Task {
                        await state.addEventContinuation(id: id, continuation: continuation)
                        continuation.onTermination = { _ in
                            Task {
                                await state.removeEventContinuation(id: id)
                            }
                        }
                    }
                }
            },

            cancelAllTasks: {
                // Immediately cancel the URLSession upload task (synchronous)
                videoUploadClient.cancelCurrentUpload()

                // Then clean up state
                await state.cancelAllTasks()
            },
            resetWithErrors: {
                await state.resetWithErrors()
            },
            fetchMostRecentTask: {
                await state.getAllTasksSortedByDate()
            }
        )
    }

    private static func createVideoHook(
        clipId: String,
        s3Id: String,
        uploadId: String,
        clipStartTime: Double,
        clipEndTime: Double,
        sourceVideoStartTime: Double,
        sourceVideoEndTime: Double,
        caption: String,
        videoVolumeLevel: Float,
        snippetVolumeLevel: Float,
        showLyrics: Bool,
        allowComments: Bool
    ) async throws -> String {
        @Dependency(APIClientV2.self) var api

        let hookPublishSchema = HookPublishSchema(
            allowComments: allowComments,
            caption: caption.isEmpty ? "" : caption,
            isPublic: true,
            pingToProfile: false,
            showLyrics: showLyrics,
            title: ""
        )

        let videoRenderSongSchema = VideoRenderSongSchema(
            clipEndTime: clipEndTime,
            clipStartTime: clipStartTime,
            lyric: nil,
            songId: clipId,
            volume: Int(snippetVolumeLevel * 100)
        )

        let videoRenderVideoSchema = VideoRenderVideoSchema(
            endTime: clipEndTime - clipStartTime,
            s3Id: s3Id,
            sourceEndTime: sourceVideoEndTime,
            sourceStartTime: sourceVideoStartTime,
            startTime: 0.0,
            uploadId: uploadId,
            volume: Int(videoVolumeLevel * 100)
        )

        let videoRenderSchema = VideoRenderSchema(
            images: nil,
            shader: nil,
            song: videoRenderSongSchema,
            textSticker: nil,
            videos: [videoRenderVideoSchema]
        )

        let request = VideoHookCreationRequest(
            clipId: clipId,
            hookPublish: hookPublishSchema,
            videoRender: videoRenderSchema
        )

        let response: VideoHookCreationResponse
        do {
            response = try await api.createVideoHook(request)
        } catch {
            let errorDetail = error.underlyingApiError?.errorDetail ?? error.underlyingError ?? "Unknown error"
            let errorCode = (error as NSError).code
            log.telemetry.error(error, message: "HooksPostClient: createVideoHook API failed - clipId: \(clipId), uploadId: \(uploadId), s3Id: \(s3Id), errorDetail: \(errorDetail), errorCode: \(errorCode)")
            throw error
        }

        if let hookId = response.id {
            log.info("HooksPostClient: createVideoHook successful - hookId: \(hookId)")
            return hookId
        } else {
            log.telemetry.error(HooksPostError.hookCreationFailed("No hook ID returned from API"), message: "HooksPostClient: createVideoHook failed - clipId: \(clipId), uploadId: \(uploadId), response: \(String(describing: response))")
            throw HooksPostError.hookCreationFailed("No hook ID returned from API")
        }
    }

    private static func startAwaitingHookReadiness(
        hookId: String,
        state: HooksPostState,
        clock: any Clock<Duration>,
        thumbnailImage: UIImage?,
        videoUploadId: String?
    ) async throws {
        @Dependency(APIClientV2.self) var api
        @Dependency(\.eventBus) var eventBus

        let taskId = UUID().uuidString
        let pollingTask = HooksPostClientTask(
            id: taskId,
            type: .awaitHookReadiness(hookId: hookId),
            status: .awaitingHookReadiness,
            thumbnailImage: thumbnailImage
        )

        try await state.appendTask(pollingTask)
        let startEvent = HooksPostClientEvent.statusChanged(
            taskId: taskId,
            status: .awaitingHookReadiness,
            thumbnailImage: thumbnailImage,
            videoUploadId: videoUploadId
        )
        await state.broadcastEvent(startEvent)

        // Immediately fetch and send triggeredHookCreation event
        do {
            let currentRenderingHook = try await api.getHookStatus(hookId)
            let processingHook = try Hook(currentRenderingHook)
            eventBus.sendHookEvent(.triggeredHookCreation(processingHook))

            // Log hook details for debugging
            let videoUploadIdsStr = currentRenderingHook.videoUploadIds?.joined(separator: ", ") ?? "none"
            log.info("HooksPostClient: hookCreated - hookId: \(currentRenderingHook.id), status: \(String(describing: currentRenderingHook.status)), videoUploadIds: [\(videoUploadIdsStr)]")
        } catch {
            let errorDetail = error.underlyingApiError?.errorDetail ?? error.underlyingError ?? "Unknown error"
            log.telemetry.error(error, message: "HooksPostClient: initialGetHookStatus failed - hookId: \(hookId), errorDetail: \(errorDetail)")
        }

        let pollingTaskHandle = Task {
            var pollCount = 0
            let pollingStartTime = Date()
            do {
                for await _ in clock.timer(interval: .seconds(3)) {
                    pollCount += 1
                    let elapsed = Date().timeIntervalSince(pollingStartTime)

                    if await state.checkCancellation() {
                        break
                    }

                    let currentRenderingHook = try await api.getHookStatus(hookId)

                    if currentRenderingHook.status == .renderedPassedModeration {
                        await state.updateTask(id: taskId, status: .hookReady)
                        let statusEvent = HooksPostClientEvent.statusChanged(
                            taskId: taskId,
                            status: .hookReady,
                            thumbnailImage: thumbnailImage,
                            videoUploadId: videoUploadId
                        )
                        await state.broadcastEvent(statusEvent)

                        do {
                            let createdHook = try Hook(currentRenderingHook)
                            eventBus.sendHookEvent(.hookCreated(hook: createdHook))
                        } catch {
                            let videoUploadIdsStr = currentRenderingHook.videoUploadIds?.joined(separator: ", ") ?? "none"
                            log.telemetry.error(error, message: "HooksPostClient: Hook construction failed - hookId: \(currentRenderingHook.id), status: \(String(describing: currentRenderingHook.status)), videoUploadIds: [\(videoUploadIdsStr)]")
                        }

                        // Clear uploadId after terminal state
                        await state.updateTaskUploadId(id: taskId, uploadId: nil)
                        await state.cleanupCompletedTasks()
                        break
                    } else if currentRenderingHook.status == .renderedFailedModeration {
                        let videoUploadIdsStr = currentRenderingHook.videoUploadIds?.joined(separator: ", ") ?? "none"
                        log.telemetry.error(HooksPostError.hookStatusError, message: "HooksPostClient: hookFailedModeration - hookId: \(currentRenderingHook.id), status: \(String(describing: currentRenderingHook.status)), videoUploadIds: [\(videoUploadIdsStr)], pollCount: \(pollCount), elapsed: \(elapsed)s")

                        await state.updateTask(id: taskId, status: .hookFailedModeration)
                        let statusEvent = HooksPostClientEvent.statusChanged(
                            taskId: taskId,
                            status: .hookFailedModeration,
                            thumbnailImage: thumbnailImage,
                            videoUploadId: videoUploadId
                        )
                        await state.broadcastEvent(statusEvent)

                        // Clean up completed tasks
                        // Clear uploadId after terminal state
                        await state.updateTaskUploadId(id: taskId, uploadId: nil)
                        await state.cleanupCompletedTasks()

                        break
                    } else if currentRenderingHook.status == .error {
                        let videoUploadIdsStr = currentRenderingHook.videoUploadIds?.joined(separator: ", ") ?? "none"
                        log.telemetry.error(HooksPostError.hookStatusError, message: "HooksPostClient: hookProcessingFailed - hookId: \(currentRenderingHook.id), status: \(String(describing: currentRenderingHook.status)), videoUploadIds: [\(videoUploadIdsStr)], pollCount: \(pollCount), elapsed: \(elapsed)s")

                        await state.updateTask(id: taskId, status: .hookProcessingFailed)
                        let statusEvent = HooksPostClientEvent.statusChanged(
                            taskId: taskId,
                            status: .hookProcessingFailed,
                            thumbnailImage: thumbnailImage,
                            videoUploadId: videoUploadId
                        )
                        await state.broadcastEvent(statusEvent)

                        // Clean up completed tasks
                        // Clear uploadId after terminal state
                        await state.updateTaskUploadId(id: taskId, uploadId: nil)
                        await state.cleanupCompletedTasks()

                        break
                    }
                    // If still "pending", continue polling
                }
            } catch {
                let elapsed = Date().timeIntervalSince(pollingStartTime)
                if !(await state.checkCancellation()) {
                    let postError = HooksPostError.hookCreationFailed("Hook readiness wait failed: \(error.localizedDescription)")
                    let errorDetail = error.underlyingApiError?.errorDetail ?? error.underlyingError ?? "Unknown error"
                    log.telemetry.error(error, message: "HooksPostClient: hookPollingFailed - hookId: \(hookId), pollCount: \(pollCount), elapsed: \(elapsed)s, errorDetail: \(errorDetail)")

                    await state.updateTask(id: taskId, status: .hookProcessingFailed)
                    let statusEvent = HooksPostClientEvent.statusChanged(taskId: taskId, status: .hookProcessingFailed, thumbnailImage: nil, videoUploadId: videoUploadId)
                    await state.broadcastEvent(statusEvent)

                    // Clean up completed tasks
                    // Clear uploadId after terminal state
                    await state.updateTaskUploadId(id: taskId, uploadId: nil)
                    await state.cleanupCompletedTasks()
                }
            }
        }
    }
}

public extension DependencyValues {
    var hooksPostClient: HooksPostClient {
        get { self[HooksPostClient.self] }
        set { self[HooksPostClient.self] = newValue }
    }
}

// MARK: - Pending Hook Creation

public extension HooksPostClientStatus {
    /// Creates a pending Hook for displaying in the UI while the hook is being processed
    /// - Parameters:
    ///   - videoUploadId: Optional video upload ID to track the pending hook
    /// - Returns: A Hook with minimal data and appropriate status for UI display
    func toPendingHook(videoUploadId: String?) -> Hook {
        var ids: [String]?
        if let videoUploadId { ids = [videoUploadId] }

        // Map HooksPostClientStatus to Hook.Status
        let hookStatus: Hook.Status = switch self {
        case .hookReady:
            .renderedPassedModeration
        case .uploadVideoFailed, .createHookFailed, .hookProcessingFailed:
            .failed
        case .hookFailedModeration:
            .renderedFailedModeration
        default:
            .processing
        }

        return Hook(
            id: "pending-hooks-post",
            allowComments: true,
            caption: nil,
            clip: nil,
            commentCount: 0,
            createdAt: Date(),
            currentUserLiked: false,
            currentUserFollowsCreator: false,
            endClipTimestamp: 0,
            likeCount: 0,
            lyricDisplay: nil,
            originalClipId: "",
            recommendationItemId: nil,
            renderedVideoPreviewS3Id: nil,
            renderedVideoPreviewUrl: nil,
            renderedVideoS3Id: nil,
            renderedVideoUrl: nil,
            showLyrics: true,
            startClipTimestamp: 0,
            status: hookStatus,
            streamingUrl: nil,
            thumbnailImageS3Id: nil,
            thumbnailImageUrl: nil,
            title: "",
            updatedAt: Date(),
            user: nil,
            userId: 0,
            videoDuration: 0,
            viewCount: 0,
            isDisliked: false,
            creationSource: .userUpload,
            contentRatingTags: nil,
            videoUploadIds: ids
        )
    }
}
