import Foundation
import OSLog

private let logger = Logger(subsystem: "nahbar", category: "TourCoordinator")

// MARK: - TourCoordinator

/// Observable coordinator that drives the guided tour flow.
/// Passed as an environment object from NahbarApp.
@Observable
final class TourCoordinator {

    // MARK: Observed State (trigger SwiftUI re-renders)

    private(set) var activeTour: Tour?
    private(set) var currentStepIndex: Int = 0

    // MARK: Non-Observed Internal State

    @ObservationIgnored private var pendingQueue: [TourID] = []
    @ObservationIgnored private var onTourComplete: (() -> Void)?
    @ObservationIgnored private let tours: [Tour]
    @ObservationIgnored private let seenStore: TourSeenStore
    @ObservationIgnored private let appVersionProvider: () -> String

    // MARK: Init

    init(
        tours: [Tour] = TourCatalog.all,
        seenStore: TourSeenStore = TourSeenStore(),
        appVersionProvider: @escaping () -> String = {
            Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0"
        }
    ) {
        self.tours = tours
        self.seenStore = seenStore
        self.appVersionProvider = appVersionProvider
    }

    // MARK: Computed

    var currentStep: TourStep? {
        guard let tour = activeTour, currentStepIndex < tour.steps.count else { return nil }
        return tour.steps[currentStepIndex]
    }

    var isActive: Bool { activeTour != nil }

    var isFirstStep: Bool { currentStepIndex == 0 }

    var isLastStep: Bool {
        guard let tour = activeTour else { return false }
        return currentStepIndex == tour.steps.count - 1
    }

    var stepCount: Int { activeTour?.steps.count ?? 0 }

    // MARK: Auto-Start

    /// Checks for update tours that should be started automatically.
    /// Call this from ContentView.onAppear or NahbarApp.task.
    func checkForPendingTours() {
        let currentVersion = appVersionProvider()
        let lastSeen = seenStore.lastSeenAppVersion ?? ""

        let pending = tours.filter { tour in
            guard tour.triggerMode == .autoOnUpdate else { return false }
            return !seenStore.hasSeen(tour.id) && versionIsNewer(tour.minAppVersion, than: lastSeen)
        }

        if !pending.isEmpty {
            logger.info("Ausstehende Tours gefunden: \(pending.map { $0.id.rawValue })")
            pendingQueue = pending.map { $0.id }
            startNextInQueue()
        }
    }

    // MARK: Tour Control

    /// Starts a tour by ID. An optional `onComplete` closure is called when the tour finishes
    /// (whether by completing all steps, skipping, or closing).
    func start(_ id: TourID, onComplete: (() -> Void)? = nil) {
        guard let tour = tours.first(where: { $0.id == id }) else {
            logger.warning("Tour nicht gefunden: \(id.rawValue)")
            return
        }
        logger.info("Tour gestartet: \(id.rawValue)")
        onTourComplete = onComplete
        currentStepIndex = 0
        activeTour = tour
    }

    func next() {
        guard let tour = activeTour else { return }
        if currentStepIndex < tour.steps.count - 1 {
            currentStepIndex += 1
        } else {
            completeTour()
        }
    }

    func previous() {
        guard currentStepIndex > 0 else { return }
        currentStepIndex -= 1
    }

    /// Skips the tour and marks it as seen.
    func skip() { completeTour() }

    /// Closes the tour (semantically identical to skip in v1) and marks it as seen.
    func close() { completeTour() }

    /// `true` when the onboarding tour has already been completed or skipped.
    var hasSeenOnboardingTour: Bool {
        seenStore.hasSeen(.onboarding)
    }

    /// Starts the onboarding tour if it hasn't been seen yet.
    /// Call this after first-launch onboarding completes and the main app is visible.
    func startOnboardingTourIfNeeded() {
        guard !seenStore.hasSeen(.onboarding) else { return }
        start(.onboarding)
    }

    /// Resets the "seen" state for all tours. Call this when the app data is reset.
    func resetSeenTours() {
        seenStore.reset()
    }

    // MARK: Target Frames (populated by tourPresenter overlay)

    /// Not observed — updated by the tourPresenter via preference keys.
    @ObservationIgnored var targetFrames: [TourTargetID: CGRect] = [:]

    // MARK: Private

    private func completeTour() {
        guard let tour = activeTour else { return }
        seenStore.markSeen(tour.id)

        // Capture and clear callbacks before resetting state
        let completion = onTourComplete
        onTourComplete = nil
        activeTour = nil
        currentStepIndex = 0

        logger.info("Tour abgeschlossen: \(tour.id.rawValue)")
        completion?()

        // If pending queue is now empty, update the stored version
        if pendingQueue.isEmpty {
            seenStore.lastSeenAppVersion = appVersionProvider()
        }

        startNextInQueue()
    }

    private func startNextInQueue() {
        guard !pendingQueue.isEmpty, activeTour == nil else { return }
        let nextID = pendingQueue.removeFirst()
        start(nextID)
    }

    private func versionIsNewer(_ version: String, than other: String) -> Bool {
        guard !version.isEmpty else { return false }
        if other.isEmpty { return true }
        return version.compare(other, options: .numeric) == .orderedDescending
    }
}
