import Combine
import SwiftUI

final class HooksCameraViewModel: ObservableObject {
    private let cameraManager = HooksCameraManager()
    private var previewStreamTask: Task<Void, Never>?

    @Published var currentFrame: CGImage?
    @Published var isOverlayDisabled: Bool = false
    @Published var completionRatio: CGFloat = 0.0
    @Published var isTapOrHoldInstructionShown: Bool = false
    @Published var isPillActive: Bool = false
    @Published var shouldUseFlashDuringRecording: Bool = false
    @Published var shouldUseTimerDuringRecording: Bool = false
    @Published var isShowingTimer: Bool = false
    @Published var timerCount: Int = 3 // TODO: This should be consolidated or passed in
    @Published var buttonState: HooksCameraRecordButtonState = .readyToRecord
    @Published var cameraPermissionState: HooksInputPermissionState = .requesting

    @Published var songStartDate: Date = .now
    @Published var songActiveDate: Date = .now
    var songElapsedTime: TimeInterval {
        return songActiveDate.timeIntervalSince(songStartDate)
    }

    var isRecording: Bool {
        guard case .recording = buttonState else { return false }
        return true
    }

    var pauseSong: () -> Void = {}
    var resetSongToStart: () -> Void = {}
    var onVideoRecordingStart: () -> Void = {}
    var onVideoCapture: (URL, TimeInterval) -> Void = { _, _ in }
    var onBackButtonTap: () -> Void = {}
    var onMediaPickerTap: () -> Void = {}
    var onSongPillTap: () -> Void = {}

    enum Action {
        case onBackButtonTap
        case onMediaPickerTap

        case checkPermissionState

        case startWritingAssetWithTimerDelay
        case startWritingAsset
        case endWritingAsset
        case toggleCamera
        case startCameraPreviews
        case cancelCameraPreviews
        case onSongPillTap

        case resetSongToStart
        case toggleFlash
        case toggleTimer
    }

    init() {
        cameraManager.delegate = self
    }

    func send(_ action: Action) {
        switch action {
        case .onSongPillTap:
            onSongPillTap()

        case .toggleFlash:
            shouldUseFlashDuringRecording = !shouldUseFlashDuringRecording

        case .toggleTimer:
            shouldUseTimerDuringRecording = !shouldUseTimerDuringRecording

        case .checkPermissionState:
            checkPermissionState()

        case .onBackButtonTap:
            onBackButtonTap()

        case .onMediaPickerTap:
            onMediaPickerTap()

        case .toggleCamera:
            cameraManager.flipCamera()

        case .startWritingAssetWithTimerDelay:
            startCountDown()

        case .startWritingAsset:
            if shouldUseFlashDuringRecording {
                cameraManager.setFlash(on: true)
            }

            onVideoRecordingStart()
            startSongFromStart()
            cameraManager.startWriting()

        case .endWritingAsset:
            cameraManager.setFlash(on: false)
            cameraManager.endWriting()

        case .startCameraPreviews:
            startCameraPreviews()

        case .cancelCameraPreviews:
            cancelCameraPreviewTask()

        case .resetSongToStart:
            resetSongToStart()
        }
    }

    func onLyricsTimeUpdate(
        _: String,
        _: TimeInterval,
        _: TimeInterval
    ) {
        songActiveDate = .now

        if songElapsedTime > 30.0 { // TODO: This should be consolidated or passed in
            startSongFromStart()
        }
    }
}

extension HooksCameraViewModel: HooksCameraManagerDelegate {
    func didStartWritingVideoAsset(_: URL?) {
        Task { @MainActor in
            self.buttonState = .recording("")
        }
    }

    func didUpdateWritingVideoAsset(_ formattedTime: String, completionRatio: CGFloat) {
        Task { @MainActor in
            self.buttonState = .recording(formattedTime)
            self.completionRatio = completionRatio
            if completionRatio >= 1.0 {
                send(.endWritingAsset)
            }
        }
    }

    func didFinishRecording() {
        Task { @MainActor [weak self] in
            self?.buttonState = .processing
            self?.cancelCameraPreviewTask()
        }
    }

    func didEndWritingVideoAsset(_ to: URL?, duration: TimeInterval) {
        Task { @MainActor in
            self.buttonState = .processing
            guard let url = to else { return }
            self.onVideoCapture(url, duration)
        }
    }
}

private extension HooksCameraViewModel {
    func startSongFromStart() {
        songStartDate = .now
        send(.resetSongToStart)
    }

    func checkPermissionState() {
        Task {
            await MainActor.run {
                cameraPermissionState = .requesting
            }
            let isAuthorized = await cameraManager.isAuthorized
            await MainActor.run { [isAuthorized = isAuthorized] in
                if isAuthorized {
                    cameraPermissionState = .permitted
                } else {
                    cameraPermissionState = .notPermitted
                }
            }
        }
    }

    func startCameraPreviews() {
        previewStreamTask = Task {
            for await image in cameraManager.previewStream {
                Task { @MainActor in
                    currentFrame = image
                }
            }
        }
    }

    func cancelCameraPreviewTask() {
        previewStreamTask?.cancel()
        previewStreamTask = nil
    }

    func startCountDown() {
        Task {
            await MainActor.run {
                buttonState = .recording("0:00")
                timerCount = 3
                isShowingTimer = true
                pauseSong()
            }
            try? await Task.sleep(for: .seconds(1.0))
            await MainActor.run {
                timerCount = 2
            }

            try? await Task.sleep(for: .seconds(1.0))
            await MainActor.run {
                timerCount = 1
                isShowingTimer = true
            }

            try? await Task.sleep(for: .seconds(1.0))
            await MainActor.run {
                timerCount = 0
                isShowingTimer = false
                send(.startWritingAsset)
            }
        }
    }
}
