//
//  Class.swift
//  Adwaita
//
//  Created by david-swift on 14.01.24.
//

import Foundation

/// A class.
struct Class: Decodable {

    /// The name of the class.
    var name: String
    /// The prefix for C symbols.
    var cSymbolPrefix: String
    /// The type in C.
    var cType: String?
    /// The parent class.
    var parent: String?

    /// The doc string.
    var doc: String
    /// The available initializers.
    var constructors: [Constructor]
    /// The available properties.
    var properties: [Property]
    /// The available signals (callbacks).
    var signals: [Signal]

    /// The coding keys for the class
    enum CodingKeys: String, CodingKey {

        /// Coding key
        case name, cSymbolPrefix, cType, parent, doc
        /// Coding key
        case constructors = "constructor"
        /// Coding key
        case properties = "property"
        /// Coding key
        case signals = "glibSignal"

    }

    /// Get the properties of the class and its parent classes.
    /// - Parameters:
    ///     - classes: The classes in the namespace.
    ///     - configurations: The configurations for the classes in the namespace.
    /// - Returns: The properties.
    func properties(classes: [Self], configurations: [WidgetConfiguration]) -> [Property] {
        if let parentClass = parentClass(classes: classes) {
            return properties + parentClass
                .properties(classes: classes, configurations: configurations)
                .map { property in
                    var property = property
                    if property.prefix == nil {
                        property.prefix = parentClass.prefix()
                        property.cast = configurations.first { $0.class == parentClass.name }?.cast ?? false
                    }
                    return property
                }
        }
        return properties
    }

    /// Get the signals of the class and its parent classes.
    /// - Parameter classes: The classes in the namespace.
    /// - Returns: The signals.
    func signals(classes: [Self]) -> [Signal] {
        if let parentClass = parentClass(classes: classes) {
            return signals + parentClass.signals(classes: classes)
        }
        return signals
    }

    /// Get the assignments for the static widgets of the class and its parent classes.
    /// - Parameters:
    ///     - classes: The classes in the namespace.
    ///     - configs: The configurations.
    /// - Returns: The code.
    func staticWidgets(classes: [Self], configs: [WidgetConfiguration]) -> String {
        var content = ""
        if let parentClass = parentClass(classes: classes) {
            content += parentClass.staticWidgets(classes: classes, configs: configs)
        }
        guard let config = configs.first(where: { $0.class == name }) else {
            return content
        }
        let widgetPointer = config.cast ? "storage.pointer?.cast()" : "storage.pointer"
        for widget in config.staticWidgets {
            content += """

                    var \(widget.name)Storage: [ViewStorage] = []
                    for view in \(widget.name)() {
                        \(widget.name)Storage.append(view.storage(modifiers: modifiers))
                        \(widget.add)(\(widgetPointer), \(widget.name)Storage.last?.pointer?.cast())
                    }
                    storage.content["\(widget.name)"] = \(widget.name)Storage
            """
        }
        return content
    }

    /// Get the code for the properties of the static widgets.
    /// - Parameters:
    ///     - classes: The classes in the namespace.
    ///     - configs: The configurations.
    /// - Returns: The code.
    func staticWidgetProperties(classes: [Self], configs: [WidgetConfiguration]) -> String {
        var content = ""
        if let parentClass = parentClass(classes: classes) {
            content += parentClass.staticWidgetProperties(classes: classes, configs: configs)
        }
        guard let config = configs.first(where: { $0.class == name }) else {
            return content
        }
        for staticWidget in config.staticWidgets {
                content += """

                    /// The body for the widget "\(staticWidget.name)".
                    var \(staticWidget.name): () -> Body = { [] }
                """
        }
        return content
    }

    /// Get the parent class.
    /// - Parameter classes: The classes in the namespace.
    /// - Returns: The class.
    func parentClass(classes: [Self]) -> Self? {
        if let parent = classes.first(where: { $0.name == parent }), parent.name != "Widget" {
            return parent
        }
        return nil
    }

    /// Get the widget's prefix (e.g. "adw_preferences_row").
    /// - Returns: The prefix.
    func prefix() -> String {
        if cType?.hasPrefix("Adw") ?? false {
            return "adw_\(cSymbolPrefix)"
        } else {
            return "gtk_\(cSymbolPrefix)"
        }
    }

    // swiftlint:disable function_body_length line_length
    /// Generate the code for the class.
    /// - Parameters:
    ///     - config: The widget configuration.
    ///     - genConfig: The generation configuration.
    ///     - classes: The available classes.
    ///     - configs: The available widget configurations.
    /// - Returns: The code.
    func generate(
        config: WidgetConfiguration,
        genConfig: GenerationConfiguration,
        classes: [Self],
        configs: [WidgetConfiguration]
    ) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd.MM.yy"
        let widgetName = config.name ?? config.class
        let definition: String
        if config.dynamicWidget == nil {
            definition = "\(widgetName): Widget"
        } else {
            definition = "\(widgetName)<Element>: Widget where Element: Identifiable"
        }
        return """
        //
        //  \(widgetName).swift
        //  Adwaita
        //
        //  Created by auto-generation on \(dateFormatter.string(from: .now)).
        //

        import CAdw
        import LevenshteinTransformations

        \(doc.docComment())
        public struct \(definition) {

            /// Additional update functions for type extensions.
            var updateFunctions: [(ViewStorage) -> Void] = []
            /// Additional appear functions for type extensions.
            var appearFunctions: [(ViewStorage) -> Void] = []
        \(generateProperties(config: config, genConfig: genConfig, classes: classes, configs: configs))

            /// Initialize `\(widgetName)`.
            \(generateAdwaitaInitializer(config: config, genConfig: genConfig, classes: classes, configs: configs))

            /// 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(\(generateInitializer(name: widgetName, config: config, classes: classes, configs: configs))?.opaque())
                update(storage, modifiers: modifiers, updateProperties: true)
        \(generateWidgetAssignments(config: config, genConfig: genConfig, classes: classes, configs: configs))
        \(generateBindingAssignments(config: config, genConfig: genConfig, classes: classes, configs: configs))
                for function in appearFunctions {
                    function(storage)
                }
                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) {\(generateSignalModifications(config: config, genConfig: genConfig, classes: classes))
                storage.modify { widget in
        \(generateModifications(config: config, genConfig: genConfig, classes: classes, configs: configs))
        \(generateDynamicWidgetUpdate(config: config, genConfig: genConfig))
                }
                for function in updateFunctions {
                    function(storage)
                }
            }
        \(generateModifiers(config: config, genConfig: genConfig, classes: classes, configs: configs))
        }

        """
    }
    // swiftlint:enable function_body_length line_length

}
