//
//  ViewStack.swift
//  Adwaita
//
//  Created by david-swift on 30.12.23.
//

import CAdw

/// A widget holding multiple children but only displaying one.
public struct ViewStack: AdwaitaWidget {

    /// The stack's active content.
    var content: Body
    /// The stack's active identifier.
    var id: CustomStringConvertible
    /// Whether the view stack allocates the same height and width for all children.
    var homogeneous: Bool?
    /// Whether to limit the stack to one child (always redraw the UI when the id changes).
    var limitChildren: Bool?

    /// Initialize the stack.
    /// - Parameters:
    ///     - id: The identifier of the current view.
    ///     - view: The current view.
    public init<Identifier>(
        id: Identifier,
        @ViewBuilder view: @escaping (Identifier) -> Body
    ) where Identifier: CustomStringConvertible {
        content = view(id)
        self.id = id
    }

    /// Initialize the stack.
    /// - Parameters:
    ///     - element: The current identifiable element.
    ///     - view: The current view.
    public init<Element>(
        element: Element,
        @ViewBuilder view: @escaping (Element) -> Body
    ) where Element: Identifiable, Element.ID: CustomStringConvertible {
        content = view(element)
        self.id = element.id
    }

    /// The view storage.
    /// - Parameters:
    ///     - modifiers: Modify views before being updated.
    ///     - type: The view render data type.
    /// - Returns: The view storage.
    public func container<Data>(
        data: WidgetData,
        type: Data.Type
    ) -> ViewStorage where Data: ViewRenderData {
        let stack = gtk_stack_new()
        let storage = ViewStorage(stack?.opaque())
        update(storage, data: data, updateProperties: true, type: type)
        return storage
    }

    /// Update the stored content.
    /// - Parameters:
    ///     - storage: The storage to update.
    ///     - modifiers: Modify views before being updated
    ///     - updateProperties: Whether to update the view's properties.
    ///     - type: The view render data type.
    public func update<Data>(
        _ storage: ViewStorage,
        data: WidgetData,
        updateProperties: Bool,
        type: Data.Type
    ) where Data: ViewRenderData {
        if let view = storage.content[id.description]?.first {
            content.updateStorage(view, data: data, updateProperties: updateProperties, type: type)
        } else {
            let view = content.storage(data: data, type: type)
            gtk_stack_add_named(storage.opaquePointer, view.opaquePointer?.cast(), id.description)
            storage.content[id.description] = [view]
        }
        if let visibleView = storage.content[id.description]?.first {
            if let transition = visibleView.fields[.transition] as? Transition {
                gtk_stack_set_transition_type(storage.opaquePointer, transition.cTransition)
            }
            if (storage.previousState as? Self)?.id.description != id.description {
                gtk_stack_set_visible_child_name(storage.opaquePointer, id.description)
            }
        }
        if let homogeneous, homogeneous != (storage.previousState as? Self)?.homogeneous {
            gtk_stack_set_vhomogeneous(storage.opaquePointer, homogeneous.cBool)
            gtk_stack_set_hhomogeneous(storage.opaquePointer, homogeneous.cBool)
        }
        if let limitChildren, limitChildren {
            for element in storage.content where element.key != id.description {
                guard let first = element.value.first else {
                    continue
                }
                gtk_stack_remove(storage.opaquePointer, first.opaquePointer?.cast())
                storage.content.removeValue(forKey: element.key)
            }
        }
        storage.previousState = self
    }

    /// Whether the view stack allocates the same height and width for all children.
    /// - Parameter homogeneous: Whether this is enabled.
    /// - Returns: The stack.
    public func homogeneous(_ homogeneous: Bool? = true) -> Self {
        modify { $0.homogeneous = homogeneous }
    }

    /// Whether to limit the stack to one child (always redraw the UI when the id changes).
    /// - Parameter limit: Whether to limit the stack to one child.
    /// - Returns: The stack.
    public func limitChildren(_ limitChildren: Bool? = true) -> Self {
        modify { $0.limitChildren = limitChildren }
    }

}
