//
//  NavigationView.swift
//  Adwaita
//
//  Created by auto-generation on 21.07.24.
//

import CAdw
import LevenshteinTransformations

/// A page-based navigation container.
/// 
/// <picture><source srcset="navigation-view-dark.png" media="(prefers-color-scheme: dark)"><img src="navigation-view.png" alt="navigation-view"></picture>
/// 
/// `AdwNavigationView` presents one child at a time, similar to
/// [class@Gtk.Stack].
/// 
/// `AdwNavigationView` can only contain [class@NavigationPage] children.
/// 
/// It maintains a navigation stack that can be controlled with
/// [method@NavigationView.push] and [method@NavigationView.pop]. The whole
/// navigation stack can also be replaced using [method@NavigationView.replace].
/// 
/// `AdwNavigationView` allows to manage pages statically or dynamically.
/// 
/// Static pages can be added using the [method@NavigationView.add] method. The
/// `AdwNavigationView` will keep a reference to these pages, but they aren't
/// accessible to the user until [method@NavigationView.push] is called (except
/// for the first page, which is pushed automatically). Use the
/// [method@NavigationView.remove] method to remove them. This is useful for
/// applications that have a small number of unique pages and just need
/// navigation between them.
/// 
/// Dynamic pages are automatically destroyed once they are popped off the
/// navigation stack. To add a page like this, push it using the
/// [method@NavigationView.push] method without calling
/// [method@NavigationView.add] first.
/// 
/// ## Tags
/// 
/// Static pages, as well as any pages in the navigation stack, can be accessed
/// by their [property@NavigationPage:tag]. For example,
/// [method@NavigationView.push_by_tag] can be used to push a static page that's
/// not in the navigation stack without having to keep a reference to it manually.
/// 
/// ## Header Bar Integration
/// 
/// When used inside `AdwNavigationView`, [class@HeaderBar] will automatically
/// display a back button that can be used to go back to the previous page when
/// possible. The button also has a context menu, allowing to pop multiple pages
/// at once, potentially across multiple navigation views.
/// 
/// Set [property@HeaderBar:show-back-button] to `FALSE` to disable this behavior
/// in rare scenarios where it's unwanted.
/// 
/// `AdwHeaderBar` will also display the title of the `AdwNavigationPage` it's
/// placed into, so most applications shouldn't need to customize it at all.
/// 
/// ## Shortcuts and Gestures
/// 
/// `AdwNavigationView` supports the following shortcuts for going to the
/// previous page:
/// 
/// - <kbd>Escape</kbd> (unless [property@NavigationView:pop-on-escape] is set to
/// `FALSE`)
/// - <kbd>Alt</kbd>+<kbd>←</kbd>
/// - Back mouse button
/// 
/// Additionally, it supports interactive gestures:
/// 
/// - One-finger swipe towards the right on touchscreens
/// - Scrolling towards the right on touchpads (usually two-finger swipe)
/// 
/// These gestures have transitions enabled regardless of the
/// [property@NavigationView:animate-transitions] value.
/// 
/// Applications can also enable shortcuts for pushing another page onto the
/// navigation stack via connecting to the [signal@NavigationView::get-next-page]
/// signal, in that case the following shortcuts are supported:
/// 
/// - <kbd>Alt</kbd>+<kbd>→</kbd>
/// - Forward mouse button
/// - Swipe/scrolling towards the left
/// 
/// For right-to-left locales, the gestures and shortcuts are reversed.
/// 
/// [property@NavigationPage:can-pop] can be used to disable them, along with the
/// header bar back buttons.
/// 
/// ## Actions
/// 
/// `AdwNavigationView` defines actions for controlling the navigation stack.
/// actions for controlling the navigation stack:
/// 
/// - `navigation.push` takes a string parameter specifying the tag of the page to
/// push, and is equivalent to calling [method@NavigationView.push_by_tag].
/// 
/// - `navigation.pop` doesn't take any parameters and pops the current page from
/// the navigation stack, equivalent to calling [method@NavigationView.pop].
/// 
/// ## `AdwNavigationView` as `GtkBuildable`
/// 
/// `AdwNavigationView` allows to add pages as children, equivalent to using the
/// [method@NavigationView.add] method.
/// 
/// Example of an `AdwNavigationView` UI definition:
/// 
/// ```xml
/// <object class="AdwNavigationView"><child><object class="AdwNavigationPage"><property name="title" translatable="yes">Page 1</property><property name="child"><object class="AdwToolbarView"><child type="top"><object class="AdwHeaderBar"/></child><property name="content"><object class="GtkButton"><property name="label" translatable="yes">Open Page 2</property><property name="halign">center</property><property name="valign">center</property><property name="action-name">navigation.push</property><property name="action-target">'page-2'</property><style><class name="pill"/></style></object></property></object></property></object></child><child><object class="AdwNavigationPage"><property name="title" translatable="yes">Page 2</property><property name="tag">page-2</property><property name="child"><object class="AdwToolbarView"><child type="top"><object class="AdwHeaderBar"/></child><property name="content"><!-- ... --></property></object></property></object></child></object>
/// ```
/// 
/// <picture><source srcset="navigation-view-example-dark.png" media="(prefers-color-scheme: dark)"><img src="navigation-view-example.png" alt="navigation-view-example"></picture>
/// 
/// ## CSS nodes
/// 
/// `AdwNavigationView` has a single CSS node with the name `navigation-view`.
/// 
/// ## Accessibility
/// 
/// `AdwNavigationView` uses the `GTK_ACCESSIBLE_ROLE_GROUP` role.
public struct NavigationView: Widget {

    /// Additional update functions for type extensions.
    var updateFunctions: [(ViewStorage, [(View) -> View], Bool) -> Void] = []
    /// Additional appear functions for type extensions.
    var appearFunctions: [(ViewStorage, [(View) -> View]) -> Void] = []

    /// Whether to animate page transitions.
    /// 
    /// Gesture-based transitions are always animated.
    var animateTransitions: Bool?
    /// Whether pressing Escape pops the current page.
    /// 
    /// Applications using `AdwNavigationView` to implement a browser may want to
    /// disable it.
    var popOnEscape: Bool?
    /// Emitted when a push shortcut or a gesture is triggered.
    /// 
    /// To support the push shortcuts and gestures, the application is expected to
    /// return the page to push in the handler.
    /// 
    /// This signal can be emitted multiple times for the gestures, for example
    /// when the gesture is cancelled by the user. As such, the application must
    /// not make any irreversible changes in the handler, such as removing the page
    /// from a forward stack.
    /// 
    /// Instead, it should be done in the [signal@NavigationView::pushed] handler.
    var getNextPage: (() -> Void)?
    /// Emitted after @page has been popped from the navigation stack.
    /// 
    /// See [method@NavigationView.pop].
    /// 
    /// When using [method@NavigationView.pop_to_page] or
    /// [method@NavigationView.pop_to_tag], this signal is emitted for each of the
    /// popped pages.
    var popped: (() -> Void)?
    /// Emitted after a page has been pushed to the navigation stack.
    /// 
    /// See [method@NavigationView.push].
    var pushed: (() -> Void)?
    /// Emitted after the navigation stack has been replaced.
    /// 
    /// See [method@NavigationView.replace].
    var replaced: (() -> Void)?
    /// The application.
    var app: GTUIApp?
    /// The window.
    var window: GTUIApplicationWindow?

    /// Initialize `NavigationView`.
    public init() {
    }

    /// Get the widget's view storage.
    /// - Parameter modifiers: The view modifiers.
    /// - Returns: The view storage.
    public func container(modifiers: [(View) -> View]) -> ViewStorage {
        let storage = ViewStorage(adw_navigation_view_new()?.opaque())
        for function in appearFunctions {
            function(storage, modifiers)
        }
        update(storage, modifiers: modifiers, updateProperties: true)

        return storage
    }

    /// Update the widget's view storage.
    /// - Parameters:
    ///     - storage: The view storage.
    ///     - modifiers: The view modifiers.
    ///     - updateProperties: Whether to update the view's properties.
    public func update(_ storage: ViewStorage, modifiers: [(View) -> View], updateProperties: Bool) {
        if let getNextPage {
            storage.connectSignal(name: "get-next-page", argCount: 0) {
                getNextPage()
            }
        }
        if let popped {
            storage.connectSignal(name: "popped", argCount: 1) {
                popped()
            }
        }
        if let pushed {
            storage.connectSignal(name: "pushed", argCount: 0) {
                pushed()
            }
        }
        if let replaced {
            storage.connectSignal(name: "replaced", argCount: 0) {
                replaced()
            }
        }
        storage.modify { widget in

            if let animateTransitions, updateProperties {
                adw_navigation_view_set_animate_transitions(widget, animateTransitions.cBool)
            }
            if let popOnEscape, updateProperties {
                adw_navigation_view_set_pop_on_escape(widget, popOnEscape.cBool)
            }


        }
        for function in updateFunctions {
            function(storage, modifiers, updateProperties)
        }
    }

    /// Whether to animate page transitions.
    /// 
    /// Gesture-based transitions are always animated.
    public func animateTransitions(_ animateTransitions: Bool? = true) -> Self {
        var newSelf = self
        newSelf.animateTransitions = animateTransitions
        
        return newSelf
    }

    /// Whether pressing Escape pops the current page.
    /// 
    /// Applications using `AdwNavigationView` to implement a browser may want to
    /// disable it.
    public func popOnEscape(_ popOnEscape: Bool? = true) -> Self {
        var newSelf = self
        newSelf.popOnEscape = popOnEscape
        
        return newSelf
    }

    /// Emitted when a push shortcut or a gesture is triggered.
    /// 
    /// To support the push shortcuts and gestures, the application is expected to
    /// return the page to push in the handler.
    /// 
    /// This signal can be emitted multiple times for the gestures, for example
    /// when the gesture is cancelled by the user. As such, the application must
    /// not make any irreversible changes in the handler, such as removing the page
    /// from a forward stack.
    /// 
    /// Instead, it should be done in the [signal@NavigationView::pushed] handler.
    public func getNextPage(_ getNextPage: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.getNextPage = getNextPage
        return newSelf
    }

    /// Emitted after @page has been popped from the navigation stack.
    /// 
    /// See [method@NavigationView.pop].
    /// 
    /// When using [method@NavigationView.pop_to_page] or
    /// [method@NavigationView.pop_to_tag], this signal is emitted for each of the
    /// popped pages.
    public func popped(_ popped: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.popped = popped
        return newSelf
    }

    /// Emitted after a page has been pushed to the navigation stack.
    /// 
    /// See [method@NavigationView.push].
    public func pushed(_ pushed: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.pushed = pushed
        return newSelf
    }

    /// Emitted after the navigation stack has been replaced.
    /// 
    /// See [method@NavigationView.replace].
    public func replaced(_ replaced: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.replaced = replaced
        return newSelf
    }

}
