//
//  FlowBox.swift
//  Adwaita
//
//  Created by auto-generation on 15.08.24.
//

import CAdw
import LevenshteinTransformations

/// A `GtkFlowBox` puts child widgets in reflowing grid.
/// 
/// For instance, with the horizontal orientation, the widgets will be
/// arranged from left to right, starting a new row under the previous
/// row when necessary. Reducing the width in this case will require more
/// rows, so a larger height will be requested.
/// 
/// Likewise, with the vertical orientation, the widgets will be arranged
/// from top to bottom, starting a new column to the right when necessary.
/// Reducing the height will require more columns, so a larger width will
/// be requested.
/// 
/// The size request of a `GtkFlowBox` alone may not be what you expect;
/// if you need to be able to shrink it along both axes and dynamically
/// reflow its children, you may have to wrap it in a `GtkScrolledWindow`
/// to enable that.
/// 
/// The children of a `GtkFlowBox` can be dynamically sorted and filtered.
/// 
/// Although a `GtkFlowBox` must have only `GtkFlowBoxChild` children, you
/// can add any kind of widget to it via [method@Gtk.FlowBox.insert], and a
/// `GtkFlowBoxChild` widget will automatically be inserted between the box
/// and the widget.
/// 
/// Also see [class@Gtk.ListBox].
/// 
/// # CSS nodes
/// 
/// ```
/// flowbox
/// ├── flowboxchild
/// │   ╰── <child>├── flowboxchild
/// │   ╰── <child>┊
/// ╰── [rubberband]
/// ```
/// 
/// `GtkFlowBox` uses a single CSS node with name flowbox. `GtkFlowBoxChild`
/// uses a single CSS node with name flowboxchild. For rubberband selection,
/// a subnode with name rubberband is used.
/// 
/// # Accessibility
/// 
/// `GtkFlowBox` uses the %GTK_ACCESSIBLE_ROLE_GRID role, and `GtkFlowBoxChild`
/// uses the %GTK_ACCESSIBLE_ROLE_GRID_CELL role.
public struct FlowBox<Element>: AdwaitaWidget where Element: Identifiable {

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

/// accept-unpaired-release
    var acceptUnpairedRelease: Bool?
    /// The accessible role of the given `GtkAccessible` implementation.
    /// 
    /// The accessible role cannot be changed once set.
    var accessibleRole: String?
    /// Determines whether children can be activated with a single
    /// click, or require a double-click.
    var activateOnSingleClick: Bool?
    /// The amount of horizontal space between two children.
    var columnSpacing: UInt?
    /// Determines whether all children should be allocated the
    /// same size.
    var homogeneous: Bool?
    /// The maximum amount of children to request space for consecutively
    /// in the given orientation.
    var maxChildrenPerLine: UInt?
    /// The minimum number of children to allocate consecutively
    /// in the given orientation.
    /// 
    /// Setting the minimum children per line ensures
    /// that a reasonably small height will be requested
    /// for the overall minimum width of the box.
    var minChildrenPerLine: UInt?
    /// The amount of vertical space between two children.
    var rowSpacing: UInt?
    /// Emitted when the user activates the @box.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    var activateCursorChild: (() -> Void)?
    /// Emitted when a child has been activated by the user.
    var childActivated: (() -> Void)?
    /// Emitted when the user initiates a cursor movement.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// Applications should not connect to it, but may emit it with
    /// g_signal_emit_by_name() if they need to control the cursor
    /// programmatically.
    /// 
    /// The default bindings for this signal come in two variants,
    /// the variant with the Shift modifier extends the selection,
    /// the variant without the Shift modifier does not.
    /// There are too many key combinations to list them all here.
    /// 
    /// - <kbd>←</kbd>, <kbd>→</kbd>, <kbd>↑</kbd>, <kbd>↓</kbd>
    /// move by individual children
    /// - <kbd>Home</kbd>, <kbd>End</kbd> move to the ends of the box
    /// - <kbd>PgUp</kbd>, <kbd>PgDn</kbd> move vertically by pages
    var moveCursor: (() -> Void)?
    /// Emitted to select all children of the box,
    /// if the selection mode permits it.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>a</kbd>.
    var selectAll: (() -> Void)?
    /// Emitted when the set of selected children changes.
    /// 
    /// Use [method@Gtk.FlowBox.selected_foreach] or
    /// [method@Gtk.FlowBox.get_selected_children] to obtain the
    /// selected children.
    var selectedChildrenChanged: (() -> Void)?
    /// Emitted to toggle the selection of the child that has the focus.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default binding for this signal is <kbd>Ctrl</kbd>-<kbd>Space</kbd>.
    var toggleCursorChild: (() -> Void)?
    /// Emitted to unselect all children of the box,
    /// if the selection mode permits it.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>a</kbd>.
    var unselectAll: (() -> Void)?
    /// The dynamic widget elements.
    var elements: [Element]
    /// The dynamic widget content.
    var content: (Element) -> Body

    /// Initialize `FlowBox`.
    public init(_ elements: [Element], @ViewBuilder content: @escaping (Element) -> Body) {
        self.elements = elements
        self.content = content
    }

    /// 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 storage = ViewStorage(gtk_flow_box_new()?.opaque())
        for function in appearFunctions {
            function(storage, data)
        }
        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 activateCursorChild {
            storage.connectSignal(name: "activate-cursor-child", argCount: 0) {
                activateCursorChild()
            }
        }
        if let childActivated {
            storage.connectSignal(name: "child-activated", argCount: 1) {
                childActivated()
            }
        }
        if let moveCursor {
            storage.connectSignal(name: "move-cursor", argCount: 4) {
                moveCursor()
            }
        }
        if let selectAll {
            storage.connectSignal(name: "select-all", argCount: 0) {
                selectAll()
            }
        }
        if let selectedChildrenChanged {
            storage.connectSignal(name: "selected-children-changed", argCount: 0) {
                selectedChildrenChanged()
            }
        }
        if let toggleCursorChild {
            storage.connectSignal(name: "toggle-cursor-child", argCount: 0) {
                toggleCursorChild()
            }
        }
        if let unselectAll {
            storage.connectSignal(name: "unselect-all", argCount: 0) {
                unselectAll()
            }
        }
        storage.modify { widget in

            if let activateOnSingleClick, updateProperties, (storage.previousState as? Self)?.activateOnSingleClick != activateOnSingleClick {
                gtk_flow_box_set_activate_on_single_click(widget, activateOnSingleClick.cBool)
            }
            if let columnSpacing, updateProperties, (storage.previousState as? Self)?.columnSpacing != columnSpacing {
                gtk_flow_box_set_column_spacing(widget, columnSpacing.cInt)
            }
            if let homogeneous, updateProperties, (storage.previousState as? Self)?.homogeneous != homogeneous {
                gtk_flow_box_set_homogeneous(widget, homogeneous.cBool)
            }
            if let maxChildrenPerLine, updateProperties, (storage.previousState as? Self)?.maxChildrenPerLine != maxChildrenPerLine {
                gtk_flow_box_set_max_children_per_line(widget, maxChildrenPerLine.cInt)
            }
            if let minChildrenPerLine, updateProperties, (storage.previousState as? Self)?.minChildrenPerLine != minChildrenPerLine {
                gtk_flow_box_set_min_children_per_line(widget, minChildrenPerLine.cInt)
            }
            if let rowSpacing, updateProperties, (storage.previousState as? Self)?.rowSpacing != rowSpacing {
                gtk_flow_box_set_row_spacing(widget, rowSpacing.cInt)
            }

            var contentStorage: [ViewStorage] = storage.content[.mainContent] ?? []
            let old = storage.fields["element"] as? [Element] ?? []
            old.identifiableTransform(
                to: elements,
                functions: .init { index, element in
                    let child = content(element).storage(data: data, type: type)
                    gtk_flow_box_remove(widget, gtk_flow_box_get_child_at_index(widget, index.cInt)?.cast())
                    gtk_flow_box_insert(widget, child.opaquePointer?.cast(), index.cInt)
                    contentStorage.remove(at: index)
                    contentStorage.insert(child, at: index)
                } delete: { index in
                    gtk_flow_box_remove(widget, gtk_flow_box_get_child_at_index(widget, index.cInt)?.cast())
                    contentStorage.remove(at: index)
                } insert: { index, element in
                    let child = content(element).storage(data: data, type: type)
                    gtk_flow_box_insert(widget, child.opaquePointer?.cast(), index.cInt)
                    contentStorage.insert(child, at: index)
                }
            )
            storage.fields["element"] = elements
            storage.content[.mainContent] = contentStorage
            for (index, element) in elements.enumerated() {
                content(element).updateStorage(contentStorage[index], data: data, updateProperties: updateProperties, type: type)
            }
        }
        for function in updateFunctions {
            function(storage, data, updateProperties)
        }
        if updateProperties {
            storage.previousState = self
        }
    }

/// accept-unpaired-release
    public func acceptUnpairedRelease(_ acceptUnpairedRelease: Bool? = true) -> Self {
        var newSelf = self
        newSelf.acceptUnpairedRelease = acceptUnpairedRelease
        
        return newSelf
    }

    /// The accessible role of the given `GtkAccessible` implementation.
    /// 
    /// The accessible role cannot be changed once set.
    public func accessibleRole(_ accessibleRole: String?) -> Self {
        var newSelf = self
        newSelf.accessibleRole = accessibleRole
        
        return newSelf
    }

    /// Determines whether children can be activated with a single
    /// click, or require a double-click.
    public func activateOnSingleClick(_ activateOnSingleClick: Bool? = true) -> Self {
        var newSelf = self
        newSelf.activateOnSingleClick = activateOnSingleClick
        
        return newSelf
    }

    /// The amount of horizontal space between two children.
    public func columnSpacing(_ columnSpacing: UInt?) -> Self {
        var newSelf = self
        newSelf.columnSpacing = columnSpacing
        
        return newSelf
    }

    /// Determines whether all children should be allocated the
    /// same size.
    public func homogeneous(_ homogeneous: Bool? = true) -> Self {
        var newSelf = self
        newSelf.homogeneous = homogeneous
        
        return newSelf
    }

    /// The maximum amount of children to request space for consecutively
    /// in the given orientation.
    public func maxChildrenPerLine(_ maxChildrenPerLine: UInt?) -> Self {
        var newSelf = self
        newSelf.maxChildrenPerLine = maxChildrenPerLine
        
        return newSelf
    }

    /// The minimum number of children to allocate consecutively
    /// in the given orientation.
    /// 
    /// Setting the minimum children per line ensures
    /// that a reasonably small height will be requested
    /// for the overall minimum width of the box.
    public func minChildrenPerLine(_ minChildrenPerLine: UInt?) -> Self {
        var newSelf = self
        newSelf.minChildrenPerLine = minChildrenPerLine
        
        return newSelf
    }

    /// The amount of vertical space between two children.
    public func rowSpacing(_ rowSpacing: UInt?) -> Self {
        var newSelf = self
        newSelf.rowSpacing = rowSpacing
        
        return newSelf
    }

    /// Emitted when the user activates the @box.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    public func activateCursorChild(_ activateCursorChild: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.activateCursorChild = activateCursorChild
        return newSelf
    }

    /// Emitted when a child has been activated by the user.
    public func childActivated(_ childActivated: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.childActivated = childActivated
        return newSelf
    }

    /// Emitted when the user initiates a cursor movement.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// Applications should not connect to it, but may emit it with
    /// g_signal_emit_by_name() if they need to control the cursor
    /// programmatically.
    /// 
    /// The default bindings for this signal come in two variants,
    /// the variant with the Shift modifier extends the selection,
    /// the variant without the Shift modifier does not.
    /// There are too many key combinations to list them all here.
    /// 
    /// - <kbd>←</kbd>, <kbd>→</kbd>, <kbd>↑</kbd>, <kbd>↓</kbd>
    /// move by individual children
    /// - <kbd>Home</kbd>, <kbd>End</kbd> move to the ends of the box
    /// - <kbd>PgUp</kbd>, <kbd>PgDn</kbd> move vertically by pages
    public func moveCursor(_ moveCursor: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.moveCursor = moveCursor
        return newSelf
    }

    /// Emitted to select all children of the box,
    /// if the selection mode permits it.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>a</kbd>.
    public func selectAll(_ selectAll: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.selectAll = selectAll
        return newSelf
    }

    /// Emitted when the set of selected children changes.
    /// 
    /// Use [method@Gtk.FlowBox.selected_foreach] or
    /// [method@Gtk.FlowBox.get_selected_children] to obtain the
    /// selected children.
    public func selectedChildrenChanged(_ selectedChildrenChanged: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.selectedChildrenChanged = selectedChildrenChanged
        return newSelf
    }

    /// Emitted to toggle the selection of the child that has the focus.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default binding for this signal is <kbd>Ctrl</kbd>-<kbd>Space</kbd>.
    public func toggleCursorChild(_ toggleCursorChild: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.toggleCursorChild = toggleCursorChild
        return newSelf
    }

    /// Emitted to unselect all children of the box,
    /// if the selection mode permits it.
    /// 
    /// This is a [keybinding signal](class.SignalAction.html).
    /// 
    /// The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>a</kbd>.
    public func unselectAll(_ unselectAll: @escaping () -> Void) -> Self {
        var newSelf = self
        newSelf.unselectAll = unselectAll
        return newSelf
    }

}
