<?php
/**
 * Trait
 *
 * @version     1.0.$Revision:$
 * @version     SVN: $Id:$
 * @package     bplan-base/globals
 * @subpackage  Traits
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 * @copyright   Copyright (C) 2025, 2026 bplan-solutions GmbH & Co. KG <https://www.bplan-solutions.de/>
 * /Δ\
 */

namespace BplanBase\Globals\Traits;


use BplanBase\Globals\Support\Arr;
use Illuminate\Support\Facades\App;


/**
 * Trait
 *
 * Kapselt die Verarbeitungslogik für JSON-basierte Konfigurations-Templates
 * (`config_template`) und deren gespeicherte Einstellungen (`config_settings`).
 *
 * Verwendbar in Livewire-Formularkomponenten, die ein Modell mit einem
 * `config_template`-Feld (Typdefinition) und einem `config_settings`-Feld
 * (Instanzwerte) verwalten.
 *
 * ---
 *
 * ## JSON-Struktur für `config_template`
 *
 * Das Template kann als JSON-Array oder als JSON-Objekt angegeben werden.
 *
 * ### Minimales Array-Format:
 *
 *      [
 *          { "name": "host" },
 *          { "name": "port" }
 *      ]
 *
 * ### Minimales Objekt-Format:
 *
 *      {
 *          "host": { "name": "host" },
 *          "port": { "name": "port" }
 *      }
 *
 * Bei Objekt-Notation ist das `name`-Attribut optional, da der Schlüssel als
 * Name verwendet wird.
 *
 * ---
 *
 * ## Unterstützte Feldattribute:
 *
 *  - `crypted`    Steuert die Verschlüsselung des Wertes.
 *                 Werte: 0|1, Standard: 0
 *
 *  - `default`    Standardwert bei der Neuanlage.
 *                 Werte: mixed, Standard: null
 *
 *  - `label`      Abweichende Beschriftung des Eingabefeldes.
 *                 Werte: string, Standard: Wert aus `name`
 *
 *  - `mandatory`  Pflichtfeld.
 *                 Werte: bool, Standard: false
 *
 *  - `name`       Feldbezeichner. Pflicht bei Array-Format, optional bei Objekt-Format.
 *                 Werte: string
 *
 *  - `order`      Sortierposition innerhalb des Formulars.
 *                 Werte: integer, Standard: keiner (wird ans Ende gestellt)
 *                 Hinweis: Wird ignoriert wenn `.meta.order` vorhanden ist.
 *
 *  - `section`    Identifier der Formularsektion in der das Feld erscheint.
 *                 Werte: string, Standard: "default"
 *
 *  - `subtype`    Sub-Typ für zukünftige Validierungssteuerung (noch nicht implementiert).
 *
 *  - `type`       Typ des Eingabefeldes (noch nicht vollständig implementiert).
 *                 Werte: number|string|text|toggle, Standard: string
 *
 * ---
 *
 * ## `.meta`-Element (nur Objekt-Format):
 *
 * Das optionale `.meta`-Element ermöglicht Mehrsprachigkeit und Sortiersteuerung:
 *
 *      {
 *          ".meta": {
 *              "labels": {
 *                  "de_DE": {
 *                      "host":            "Hostname",
 *                      "port":            "Port",
 *                      "section.default": "Allgemein",
 *                      "section.network": "Netzwerk"
 *                  },
 *                  "en_GB": {
 *                      "host":            "Hostname",
 *                      "port":            "Port",
 *                      "section.default": "General",
 *                      "section.network": "Network"
 *                  }
 *              },
 *              "order": ["host", "port"],
 *              "sectionorder": ["network", "default"]
 *          },
 *          "host": { "name": "host", "section": "network" },
 *          "port": { "name": "port", "section": "network" }
 *      }
 *
 *  - `labels`       Locale-abhängige Label-Übersetzungen für Felder und Sektionen.
 *                   Sektions-Labels verwenden den Schlüssel `section.<sectionName>`.
 *
 *  - `order`        Globale Feldreihenfolge (überschreibt `order`-Attribute in Feldern).
 *                   Felder die nicht aufgelistet sind werden ans Ende gestellt.
 *
 *  - `sectionorder` Reihenfolge der Formularsektionen.
 *
 * ---
 *
 * ## Verwendung in einer Livewire-Formularkomponente:
 *
 *      use BplanBase\Globals\Traits\ProcessesConfigTemplate;
 *
 *      class Form extends BaseForm
 *      {
 *          use ProcessesConfigTemplate;
 *
 *          public array  $configSections = [];
 *          public ?array $configTemplate = null;
 *          public mixed  $configSettings = null;
 *
 *          private function _initConfigTemplate(): void
 *          {
 *              $PrinterType = PrinterType::find($this->main['printer_type_id']);
 *              $result = $this->_prepareConfigTemplate($PrinterType->config_template);
 *              $this->configTemplate = $result['template'];
 *              $this->configSections = $result['sections'];
 *          }
 *
 *          public function mount(): void
 *          {
 *              parent::mount();
 *              $this->_initConfigTemplate();
 *
 *              if ($this->exists === true) {
 *                  $this->configSettings = $this->_decodeConfigSettings($this->main['config_settings']);
 *              }
 *          }
 *
 *          protected function _onBeforeValidation(): void
 *          {
 *              parent::_onBeforeValidation();
 *              $this->main['config_settings'] = $this->_encodeConfigSettings($this->configSettings);
 *          }
 *      }
 *
 * @version     1.2.0 / 2026-05-12
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
trait ProcessesConfigTemplate
{


/* +++ OBJECT METHODS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * Verarbeitet ein JSON-Config-Template in eine formular-bereite Datenstruktur.
     *
     * Gibt ein assoziatives Array mit zwei Schlüsseln zurück:
     *  - `template`  — Das nach Sektionen gruppierte und sortierte Template-Array.
     *                  null wenn kein Template übergeben wurde.
     *  - `sections`  — Assoziatives Array der Sektions-Identifier → Sektions-Label.
     *                  Enthält immer mindestens `['default' => 'default']`.
     *
     * @param       string|null $configTemplateJson
     *
     * @return      array{template: array|null, sections: array}
     *
     * @version     1.1.0 / 2025-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _prepareConfigTemplate(?string $configTemplateJson): array
    {
        $configSections  = ['default' => 'default'];
        $preparedTemplate = null;

        if ($configTemplateJson === null) {
            return ['template' => null, 'sections' => $configSections];
        }

        $locale   = App::getLocale();
        $template = (array) json_decode($configTemplateJson);

        $preparedTemplate = ['default' => []];
        $metaLabels       = [];
        $metaOrder        = false;
        $orderKeys        = [];
        $sectionOrderKeys = [];

        if (isset($template['.meta'])) {
            $Meta = $template['.meta'];
            /*
            **  Wenn es ein .meta-Element gibt, dann ist der Array definitiv nicht nummerisch
            **  indiziert und die Elemente in $template sind Objekte.
            **  In diesem Fall wird zunächst dafür gesorgt, dass die Elemente, mit denen in der
            **  Folge gearbeitet wird, in Arrays gecastet werden. So lässt sich verhindern, dass
            **  zwei unterschiedliche Logiken implementiert werden müssen, unabhängig von der
            **  Ausgangssituation. */
            if (isset($Meta->labels->{$locale})) {
                $metaLabels = (array) $Meta->labels->{$locale};
            }
            if (isset($Meta->order)) {
                $orderKeys = (array) $Meta->order;
                $metaOrder = true;
            }
            if (isset($Meta->sectionorder)) {
                $sectionOrderKeys = (array) $Meta->sectionorder;
            }
        }

        foreach ($template as $key => $Item) {
            if ($key === '.meta' || $key === 'x.meta') {
                continue;
            }

            $item = (array) $Item;
            /*
            **  Bei einer Konfiguration mit assoziativer Indizierung ist das name-Attribut
            **  optional, weil der Name bereits im Schlüssel steckt. Um auch bei fehlendem
            **  name-Attribut im weiteren Verlauf einheitlich arbeiten zu können, wird der Name
            **  hier, falls erforderlich, ergänzt. */
            if (is_numeric($key) === false) {
                if (empty($item['name'])) {
                    $template[$key]->name = $key;
                }
            }

            if (isset($metaLabels[$key])) {
                /*
                **  Wenn es ein Label in der korrekten Sprache gibt, dann wird dieses genutzt. */
                $template[$key]->label = $metaLabels[$key];

            } elseif (isset($item['label']) === false) {
                if (isset($item['name'])) {
                    /*
                    **  Ist ein Name angegeben (der in einem nummerisch indizierten Array als
                    **  Ersatz für den fehlenden Schlüssel fungiert, in einem assoziativen Array
                    **  aber auch vom Schlüssel abweichen oder fehlen kann), dann wird dieser
                    **  Name als Label eingesetzt. */
                    $template[$key]->label = $item['name'];

                } else {
                    /*
                    **  Wenn bis hier hin kein Label gefunden werden konnte, dann wird der
                    **  aktuelle Schlüssel als Label eingesetzt.
                    **
                    **  Dieser Fall darf bei nummerischer Indizierung nie eintreten. Dort ist
                    **  die Angabe eines name-Attributs zwingend erforderlich. */
                    $template[$key]->label = $key;
                }
            }

            if ($metaOrder === false) {
                /*
                **  Für den Fall, dass es kein meta-order-Element gibt, werden die einzelnen
                **  Elemente auf das Vorhandensein eines order-Elements hin überprüft. */
                if (isset($item['order'])) {
                    $orderKeys[$key] = $item['order'];
                }
            }

            /*
            **  Weitere optionale Attribute prüfen und gegebenenfalls ergänzen. */
            if (isset($item['crypted']) === false) {
                $template[$key]->crypted = 0;
            }
            if (isset($item['default']) === false) {
                $template[$key]->default = null;
            }
            if (isset($item['mandatory']) === false) {
                $template[$key]->mandatory = false;
            }

            $section = isset($item['section']) ? $item['section'] : 'default';

            $preparedTemplate[$section][] = $template[$key];
        }

        /*
        **  Wenn es OrderKeys gibt, dann können die Felder sortiert werden. */
        if (empty($orderKeys) === false) {
            if ($metaOrder === false) {
                /*
                **  Wurden die OrderKeys aus den order-Attributen der einzelnen Elemente erstellt,
                **  dann befinden sich die für die Sortierung relevanten Daten in den Schlüsseln.
                **  Vor dem Extrahieren der Schlüssel wird der Array noch anhand seiner Werte
                **  sortiert, ohne die Schlüssel zu verändern. */
                asort($orderKeys);
                $orderKeys = array_keys($orderKeys);
            }

            Arr::ksortArray($template, $orderKeys, true);

            foreach ($preparedTemplate as $section => &$items) {
                Arr::ksortArray($items, $orderKeys, true);

                if (isset($metaLabels['section.'.$section])) {
                    $configSections[$section] = $metaLabels['section.'.$section];
                } else {
                    $configSections[$section] = $section;
                }
            }
            unset($items);
        }

        /*
        **  Wenn es SectionOrderKeys gibt, dann können auch die Formularsektionen sortiert werden. */
        if (empty($sectionOrderKeys) === false) {
            Arr::ksortArray($preparedTemplate, $sectionOrderKeys, true);
        }
        return ['template' => $preparedTemplate, 'sections' => $configSections];

    } // _prepareConfigTemplate()


    /**
     * Dekodiert einen JSON-`config_settings`-String in ein Objekt für das Formular.
     *
     * @param       string|null $configSettingsJson
     *
     * @return      object|null
     *
     * @version     1.0.0 / 2026-05-06
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _decodeConfigSettings(?string $configSettingsJson): ?object
    {
        if ($configSettingsJson === null) {
            return null;
        }

        return json_decode($configSettingsJson);

    } // _decodeConfigSettings()


    /**
     * Kodiert die Formular-Einstellungen zurück in einen JSON-String für die Datenbank.
     *
     * @param       mixed $configSettings
     *
     * @return      string|null
     *
     * @version     1.1.0 / 2026-05-12
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _encodeConfigSettings(mixed $configSettings): mixed
    {
        if ($configSettings === null) {
            return null;
        }
        return $configSettings;

    } // _encodeConfigSettings()


} // trait ProcessesConfigTemplate {}
