//  🖥️ TUIKit — Terminal UI Kit for Swift
//  State.swift
//
//  Created by LAYERED.work
//  License: MIT

import Foundation
import TUIkitCore

// MARK: - App State

/// Application state that triggers re-renders when modified.
///
/// `AppState` is thread-safe: ``setNeedsRender()`` can be called from any thread
/// (e.g., from `PulseTimer` on a background queue). Internal state is protected
/// by an `NSLock`.
///
/// The `AppRunner` subscribes to state changes and re-renders when notified.
/// Property wrappers like ``State`` and ``AppStorage`` access the shared instance
/// via ``AppState.shared``.
///
/// - Important: This is framework infrastructure. Prefer using ``State`` for reactive state
///   management in your views. Direct use of `AppState` is only necessary in advanced scenarios
///   where you manage state outside the view hierarchy.
public final class AppState: Sendable {
    /// The global shared instance.
    public static let shared = AppState()

    /// Internal state protected by a lock.
    private struct StateData: Sendable {
        var needsRender = false
        var needsCacheClear = false
        var observers: [@Sendable () -> Void] = []
    }

    /// Lock protecting all mutable state.
    private let lock = Lock(initialState: StateData())

    /// Creates a new app state instance.
    public init() {}
}

// MARK: - Public API

public extension AppState {
    /// Marks state as changed and notifies observers.
    ///
    /// This method is thread-safe and can be called from any thread.
    ///
    /// Callers that change visual output (theme, palette, appearance) do
    /// **not** need to manually clear the render cache. `RenderLoop`
    /// automatically detects environment changes via `EnvironmentSnapshot`
    /// comparison and clears the cache when needed.
    func setNeedsRender() {
        let observers = lock.withLock { state -> [@Sendable () -> Void] in
            state.needsRender = true
            return state.observers
        }
        // Call observers outside the lock to avoid potential deadlocks
        for observer in observers {
            observer()
        }
    }

    /// Marks state as changed and requests a full cache clear on next render.
    ///
    /// Called by `withObservationTracking` when an `@Observable` property
    /// changes. Unlike ``setNeedsRender()``, this also sets a flag that tells
    /// the render loop to clear the entire render cache, ensuring cached
    /// `EquatableView` subtrees re-render with the new model data.
    ///
    /// Thread-safe: can be called from any thread.
    func setNeedsRenderWithCacheClear() {
        let observers = lock.withLock { state -> [@Sendable () -> Void] in
            state.needsRender = true
            state.needsCacheClear = true
            return state.observers
        }
        for observer in observers {
            observer()
        }
    }
}

// MARK: - Internal API

extension AppState {
    /// Whether state has changed since last render.
    public var needsRender: Bool {
        lock.withLock { $0.needsRender }
    }

    /// Registers an observer to be notified of state changes.
    ///
    /// - Parameter callback: The callback to invoke on state change.
    public func observe(_ callback: @escaping @Sendable () -> Void) {
        lock.withLock { state in
            state.observers.append(callback)
        }
    }

    /// Clears all observers.
    public func clearObservers() {
        lock.withLock { state in
            state.observers.removeAll()
        }
    }

    /// Resets the needs render flag.
    public func didRender() {
        lock.withLock { state in
            state.needsRender = false
        }
    }

    /// Consumes and returns the cache-clear flag.
    ///
    /// Called by the render loop at the start of each frame. Returns `true`
    /// if any `@Observable` property changed since the last render, signaling
    /// that the render cache should be fully cleared.
    public func consumeNeedsCacheClear() -> Bool {
        lock.withLock { state in
            let value = state.needsCacheClear
            state.needsCacheClear = false
            return value
        }
    }
}

// MARK: - Hydration Context

/// The active render context used by `@State` during self-hydration.
///
/// Set by `renderToBuffer(_:context:)` before evaluating a composite view's `body`,
/// and cleared immediately after. Provides the view identity and state storage
/// that `@State.init` needs to retrieve or create persistent state.
public struct HydrationContext {
    /// The current view's structural identity.
    public let identity: ViewIdentity

    /// The persistent state storage.
    public let storage: StateStorage

    /// Creates a new hydration context.
    public init(identity: ViewIdentity, storage: StateStorage) {
        self.identity = identity
        self.storage = storage
    }
}

// MARK: - State Registration

/// Framework-internal state for `@State` self-hydration during rendering.
///
/// When `renderToBuffer(_:context:)` is about to evaluate a composite view's `body`,
/// it sets ``activeContext`` and resets ``counter`` to 0. Each `@State.init` that runs
/// during `body` evaluation checks ``activeContext``:
///
/// - **Non-nil:** Claims the next property index from ``counter`` and retrieves a
///   persistent `StateBox` from `StateStorage`.
/// - **Nil:** Creates a local `StateBox` (pre-render or outside the render tree).
///
/// This is safe because TUIKit runs on a single thread — no concurrent access.
public enum StateRegistration {
    /// The active hydration context, set during composite view body evaluation.
    ///
    /// - Important: Must be set before and cleared after each `body` call.
    ///   Nested composite views save/restore the previous context.
    nonisolated(unsafe) public static var activeContext: HydrationContext?

    /// The current property index, incremented by each `@State` during hydration.
    nonisolated(unsafe) public static var counter: Int = 0

    /// The active environment values, set during composite view body evaluation.
    ///
    /// Used by `@Environment` to read environment values during `body` evaluation.
    /// Set alongside ``activeContext`` in `renderToBuffer(_:context:)`.
    nonisolated(unsafe) public static var activeEnvironment: EnvironmentValues?
    /// Evaluates a closure with a hydration context active.
    ///
    /// Sets up `activeContext`, `counter`, and `activeEnvironment` before
    /// calling the closure, then restores the previous state. This pattern
    /// is needed whenever `view.body` is evaluated outside the normal
    /// `renderToBuffer` dispatch (e.g., in `measureChild`).
    ///
    /// - Parameters:
    ///   - context: The render context providing identity and environment.
    ///   - block: The closure to execute with hydration active.
    /// - Returns: The result of the closure.
    public static func withHydration<R>(
        context: RenderContext,
        _ block: () -> R
    ) -> R {
        let previousContext = activeContext
        let previousCounter = counter
        let previousEnvironment = activeEnvironment

        activeContext = HydrationContext(
            identity: context.identity,
            storage: context.environment.stateStorage!
        )
        counter = 0
        activeEnvironment = context.environment

        let result = block()

        activeContext = previousContext
        counter = previousCounter
        activeEnvironment = previousEnvironment

        return result
    }
}

// MARK: - Binding

/// A two-way connection to a mutable value.
///
/// `Binding` provides read and write access to a value owned elsewhere.
/// Use bindings to connect interactive views to state.
///
/// # Example
///
/// ```swift
/// struct ContentView: View {
///     @State var selectedIndex = 0
///
///     var body: some View {
///         Menu(items: menuItems, selection: $selectedIndex)
///     }
/// }
/// ```
@propertyWrapper
public struct Binding<Value> {
    /// The getter for the value.
    private let getValue: () -> Value

    /// The setter for the value.
    private let setValue: (Value) -> Void

    /// The current value.
    public var wrappedValue: Value {
        get { getValue() }
        nonmutating set { setValue(newValue) }
    }

    /// The binding itself (for projectedValue access).
    public var projectedValue: Binding<Value> {
        self
    }

    /// Creates a binding with custom getter and setter.
    ///
    /// - Parameters:
    ///   - get: The getter closure.
    ///   - set: The setter closure.
    public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
        self.getValue = get
        self.setValue = set
    }

    /// Creates a constant binding that never changes.
    ///
    /// - Parameter value: The constant value.
    /// - Returns: A binding that always returns the given value.
    public static func constant(_ value: Value) -> Binding<Value> {
        Self(get: { value }, set: { _ in })
    }
}

// MARK: - State Property Wrapper

/// A property wrapper that stores mutable state for a view.
///
/// When the value changes, the view hierarchy is re-rendered.
/// Use `@State` for simple value types owned by a single view.
///
/// # Example
///
/// ```swift
/// struct CounterView: View {
///     @State var count = 0
///
///     var body: some View {
///         VStack {
///             Text("Count: \(count)")
///             // When count changes, view re-renders
///         }
///     }
/// }
/// ```
///
/// # Accessing the Binding
///
/// Use the `$` prefix to get a `Binding` to the state:
///
/// ```swift
/// Menu(selection: $selectedIndex)
/// ```
///
/// # Render Integration
///
/// `@State` uses **self-hydrating init**: when `@State.init` runs while a
/// render context is active (`StateRegistration.activeContext`), it claims
/// the next property index and retrieves (or creates) a persistent `StateBox`
/// from `StateStorage`.
///
/// The render loop sets the active context **before** evaluating `App.body`,
/// so views constructed inside `WindowGroup { ... }` closures self-hydrate
/// immediately. For nested composite views, `renderToBuffer(_:context:)`
/// saves and restores the context around each `body` evaluation.
///
/// State is keyed by `ViewIdentity` and property index, ensuring values
/// survive view reconstruction across render passes.
///
/// Mutations signal re-renders through `AppState.shared`.
@propertyWrapper
public struct State<Value> {
    /// The backing storage box for this state value.
    ///
    /// Either a local box (when no render context is active) or a persistent
    /// box from `StateStorage` (during rendering). Since `StateBox` is a
    /// reference type, mutations through `nonmutating set` are visible everywhere.
    private let box: StateBox<Value>

    /// The default value provided at init time.
    ///
    /// Used by `StateStorage` to create a new entry when no persistent
    /// value exists for this property yet.
    let defaultValue: Value

    /// The current state value.
    public var wrappedValue: Value {
        get { box.value }
        nonmutating set { box.value = newValue }
    }

    /// A binding to the state value.
    public var projectedValue: Binding<Value> {
        Binding(
            get: { self.box.value },
            set: { self.box.value = $0 }
        )
    }

    /// Creates a state with an initial value.
    ///
    /// If a render context is active (`StateRegistration.activeContext`),
    /// the state self-hydrates: it claims a property index and retrieves
    /// or creates a persistent `StateBox` from `StateStorage`.
    ///
    /// Otherwise, a local `StateBox` is created with the default value.
    ///
    /// - Parameter wrappedValue: The initial/default value.
    public init(wrappedValue: Value) {
        self.defaultValue = wrappedValue
        if let context = StateRegistration.activeContext {
            let index = StateRegistration.counter
            StateRegistration.counter += 1
            let key = StateStorage.StateKey(identity: context.identity, propertyIndex: index)
            self.box = context.storage.storage(for: key, default: wrappedValue)
        } else {
            self.box = StateBox(wrappedValue)
        }
    }
}
