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

namespace BplanBase\Globals\Livewire\Core\TenantExternalSystem;


use App;
use BplanBase\Globals\Livewire\Core\Global\BaseForm;
use BplanBase\Globals\Models\Tenant;
use BplanBase\Globals\Repositories\ExternalSystemRepository;
use BplanBase\Globals\Repositories\TenantRepository;
use BplanBase\Globals\Support\Arr;
use StdClass;


/**
 *
 * @version     1.2.0 / 2026-02-17
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class Form extends BaseForm
{


/* +++ TRAITS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS CONSTANTS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT MEMBERS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * @var     array $configSections
     */
    public $configSections = [];


    /**
     * @var     null|StdClass $configSettings
     */
    public $configSettings;


    /**
     * @var     string $defaultSection
     */
    public $defaultSection = 'default';


    /**
     * @var    array $main
     */
    public $main = [
        'active'     => true,
        'client_code' => null,
        //'config_settings' => null,
        'identifier' => null,
        'external_system_id' => null,
        'synchronize' => false,
    ];


    /**
     * @var     array supportedSystemConfigTemplate
     */
    public $supportedSystemConfigTemplate;


    /**
     * @var     array supportedSystems
     */
    public $supportedSystems;


    /**
     * @var    array $tabs
     */
    public $tabs = [
        'base',
        'config-settings',
    ];


    /**
     * @var     array $tenant
     */
    public $tenant = [];


/* +++ CLASS MEMBERS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


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


    /**
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function _initConfigSettings(): void
    {
        if ($this->main['config_settings'] !== null) {
            $configSettings = json_decode($this->main['config_settings']);

            $this->configSettings = $configSettings;
        }
    } // _initConfigSettings()


    /**
     * Bereitet das Konfigurations-Template aus den SupportedSystems für den Aufbau des Konfigurationsformulars auf
     *
     * Für die Anlage der Konfigurations-Templates der SupportedSystems stehen verschiedene Varianten,
     * zur Auswahl. Die Templates als JSON-Array oder als JSON-Objekt eingegeben werden. Der minimale
     * Aufbau eines Templates sieht wie folgt aus.
     *
     * Als Array:
     *
     *      [
     *          {
     *              "name": "log_file"
     *          },
     *          {
     *              "name": "log_level"
     *          },
     *          {
     *              "name": "printer_name"
     *          },
     *          {
     *              "name": "temp_dir"
     *          },
     *          ...
     *      ]
     *
     * Als Objekt:
     *
     *      {
     *          "log_file": {
     *              "name": "log_file"
     *          },
     *          "log_level": {
     *              "name": "log_level"
     *          },
     *          "printer_name": {
     *              "name": "printer_name"
     *          },
     *          "temp_dir": {
     *              "name": "temp_dir"
     *          },
     *          ...
     *      }
     *
     * Für die Anzeige der Formularfelder werden mindestens ein Name und ein Label benötigt. Die
     * Angabe des label-Attributs ist trotzdem optional, weil bei ihrem Fehlen der Wert des Attributs
     * "name" verwendet wird. Genaugenommen ist bei der Objekt-Notation auch das Attribut "name"
     * bicht erforderlich, weil primär der Schlüssel verwendet wird.
     *
     * Sowohl die Array- als auch die Objekt-Variante können durch weitere Attribute angereichert
     * werden, für die Standardwerte eingesetzt werden, wenn sie fehlen.
     *
     * Folgende Attribute werden unterstützt:
     *
     *  - crypted   Steuert die Verschlüsselung der Konfigurationseinstellung.
     *              Werte: 0|1, Standard: 0
     *
     *  - default   Standardwert, der bei der Neuanlage in das Eingabefeld eingefügt wird.
     *              Werte: mixed, Standard: NULL
     *
     *              Die Anwendung dieses Parameters ist noch nicht ausimplementiert.
     *
     *  - label     Eine von "name" abweichende Zeichenkette zur Beschriftung des Eingabefeldes.
     *              Werte: string, Standard: Wert aus "name"
     *
     *  - order     Sortierreihenfolge für die Anzeige der Eingabefelder.
     *              Werte: integer, Standard: kein Standardwert
     *
     *              Elemente ohne "order" werden entsprechend ihrer Position im Template hinten an
     *              die sortierte Liste angehängt.
     *
     *              Achtung:
     *              Bei Verwendung der Objekt-Notation ist nicht gewährleistet, dass die bei der
     *              Eingabe definierte Reihenfolge der Elemente erhalten bleibt. MySQL ändert die
     *              Reihenfolge, wenn es den JSON-String in der Datenbank speichert.
     *
     *  - section   Der Name/Identifier der Formularsektion in der das Eingabefeld angezeigt werden
     *              soll.
     *              Werte: string, Standard: "default"
     *
     *  - subtype   Der Sub-Typ des Eingabefeldes.
     *              Werte: [noch nicht ausdefiniert], Standard: [noch nicht ausdefiniert]
     *
     *              Die Anwendung dieses Parameters ist noch nicht ausimplementiert.
     *              Die Idee dahinter ist, dass über diesen Wert die Validierung gesteuert wird.
     *              Beispiel: type = string, subtype = dateformat > Sicherstellung, dass ein gültiges
     *                        Datumsformat angegeben wurde.
     *
     *  - type      Der Typ des Eingabefeldes.
     *              Werte: number|string|text|toggle (noch nicht ausdefiniert), Standard: string
     *
     *              Die Anwendung dieses Parameters ist noch nicht ausimplementiert.
     *              Die Idee dahinter ist, dass über diesen Wert die Auswahl des Formularelementtyps
     *              getroffen und in Teilen vielleicht auch die Validierung gesteuert wird.
     *
     * Darüber hinaus bietet die Objekt-Variante noch die Möglichkeit ein Element mit dem Namen
     * ".meta", mit den Unterelementen "labels", "order" und "section-order", einzusetzen.
     *
     * Mit dem Unterelement "labels" ist es möglich das Formular auch mehrschprachig zu gestalten.
     * Dazu können Locale-Strings (z.B. "de_DE") als Unterschlüssel angelegt werden. Die Label
     * werden dann in Abhängigkeit von der aktuellen Sprache der Applikation ausgewählt.
     *
     * Neben den Übersetzungen für die Labels der Eingabefelder können hier auch Übersetzungen für
     * die Überschriften der Formularsektionen hinterlegt werden. Die zu verwendenden Schlüssel
     * setzen sich dabei aus dem bei der Felddefinition verwendeten section-Wert und dem Präfix
     * "section." zusammen (zum Beispiel "section.Paths").
     *
     * Beispiel:
     *
     *      {
     *          ".meta": {
     *              "labels": {
     *                  "de_DE": {
     *                      "printer_name": "Druckername",
     *                      "target": "Zielpfad",
     *                      "section.logging" => "Logging",
     *                      "section.paths" => "Pfade"
     *                  },{
     *                  "en_GB": {
     *                      "printer_name": "Printer Name",
     *                      "target": "Target-Path",
     *                      "section.logging" => "Logging",
     *                      "section.paths" => "Paths"
     *                  }
     *              }
     *          },
     *          "log_file": {
     *              "label": "Log-File",
     *              "name": "log_file",
     *              "section" => 'logging',
     *          },
     *          "log_level": {
     *              "name": "log_level",
     *              "section" => 'logging',
     *          },
     *          "printer_name": {
     *              "name": "printer_name",
     *          },
     *          "target": {
     *              "name": "target",
     *              "section": "paths"
     *          },
     *          "temp_dir": {
     *              "name": "temp_dir",
     *              "section": "paths"
     *          },
     *          ...
     *      }
     *
     * Ergebnis bei der aktuellen Applikations-Locale "de_DE":
     *
     * Formularsektion "Allgemein" (default)
     *      Feld "printer_name"      Label "Druckername"
     *
     * Formularsektion "Logging"
     *      Feld "log_file"         Label "Log-File"
     *      Feld "log_level"        Label "log_level"
     *
     * Formularsektion "Pfade"
     *      Feld "temp_dir"         Label "temp_dir"
     *      Feld "target"           Label "Zielpfad"
     *
     * Bei "order" handelt es sich um einen Array, mit dem die Reihenfolge, in der die Eingabefelder
     * im Formular angezeigt werden sollen, gesteuert werden kann.
     * Die hier angegebenen Felder werden in der angegebenen Reihenfolge ins Formular eingefügt.
     * Felder die hier nicht angegeben sind, werden am Ende angefügt.
     * Die Sortierung erfolgt unabhängig von der Sektion.
     *
     * Das meta-Element "sectionorder" erfüllt den gleichen Zweck wie das Element "order", jedoch
     * bezogen auf die Formularsektionen.
     *
     * Beispiel:
     *
     *      {
     *          ".meta": {
     *              "order": [
     *                  "printer_name",
     *                  "temp_dir",
     *                  "log_level"
     *              ],
     *              "sectionorder": [
     *                  "paths",
     *                  "logging",
     *                  "default",
     *              ],
     *
     *          },
     *          "log_file": {
     *              "label": "Log-File",
     *              "name": "log_file",
     *              "section" => 'logging',
     *          },
     *          "log_level": {
     *              "name": "log_level",
     *              "section" => 'logging',
     *          },
     *          "printer_name": {
     *              "name": "printer_name",
     *          },
     *          "target": {
     *              "name": "target",
     *              "section": "paths"
     *          },
     *          "temp_dir": {
     *              "name": "temp_dir",
     *              "section": "paths"
     *          },
     *          ...
     *      }
     *
     * Hinweis:
     * Wenn das Element ".meta > order" vorhanden ist, dann werden die order-Elemente in den
     * Felddefinitionen vollständig ignoriert.
     *
     * @todo        Mögliche weitere Optionen:
     *              - info/description
     *              - placeholder
     *
     * @todo        Generelle Validierung und Validierung auf Basis der type-Eigenschaft, muss noch
     *              implementiert werden.
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function _initSupportedSystemConfigTemplate(): void
    {
        $SupportedSystem = ExternalSystemRepository::getById($this->main['external_system_id']);

        $this->configSections['default'] = __('globals::core-tenant-external-systems.section-title.default');

        $preparedTemplate = null;

        if ($SupportedSystem->configTemplate !== null) {
            $locale = App::getLocale();

            $template = (array) json_decode($SupportedSystem->configTemplate);

            $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 je 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)) {
                    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'])) {
                    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'])) {
                    $template[$key]->crypted = 0;
                }
                if (!isset($item['default'])) {
                    $template[$key]->default = null;
                }
                if (!isset($item['default'])) {
                    $template[$key]->default = null;
                }
                if (!isset($item['mandatory'])) {
                    $template[$key]->mandatory = false;
                }
                if (!isset($item['section'])) {
                    $section = 'default';
                } else {
                    $section = $item['section'];
                }
                $preparedTemplate[$section][] = $template[$key];
            }
        }
        /*
        **  Wenn es OrderKeys gibt, dann können die Felder sortiert werden. */
        if (!empty($orderKeys)) {
            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);

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

                if (isset($metaLabels['section.'.$section])) {
                    $this->configSections[$section] = $metaLabels['section.'.$section];
                } else {
                    $this->configSections[$section] = $section;
                }
            }
        }
        /*
        **  Wenn es SectionOrderKeys gibt, dann können auch die Formularsektionen sortiert werden. */
        if (!empty($sectionOrderKeys)) {
            Arr::ksortArray($preparedTemplate, $sectionOrderKeys);
        }
        $this->supportedSystemConfigTemplate = $preparedTemplate;

    } // _initSupportedSystemConfigTemplate()


    /**
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _initSupportedSystems(): void
    {
        $this->supportedSystems = ExternalSystemRepository::getAll()
            ->pluck('label', 'id');

    } // _initSupportedSystems()


    /**
     *
     * @version     1.2.0 / 2026-02-17
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _initTenant(): void
    {
        if ($this->exists === true) {
            $Tenant = TenantRepository::getById($this->MainObject->tenant_id);

            $this->tenant = [$this->MainObject->tenant_id => $Tenant->identifier];

        } else {
            $this->main['tenant_id'] = Tenant::current()->id;

            $Tenant = TenantRepository::getById($this->main['tenant_id']);

            $this->tenant = [$this->main['tenant_id'] => $Tenant->identifier];
        }
    } // _initTenant()


    /**
     *
     * @param       bool $saveWasCreate
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _onAfterSave(bool $saveWasCreate): void
    {
        parent::_onAfterSave($saveWasCreate);

        if ($saveWasCreate === true) {
            $this->_initSupportedSystemConfigTemplate();

            if ($this->supportedSystemConfigTemplate !== null) {
                $this->tabs['config-settings'] = [
                    'active' => true,
                    'identifier' => 'config-settings',
                    'label' => __($this->localeFile.'.tab-name.config-settings'),
                    'view' => 'config-settings',
                ];
            }
        }
    } // _onAfterSave()


    /**
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _onBeforeValidation(): void
    {
        parent::_onBeforeValidation();

        if ($this->exists === false) {
            $this->_initSupportedSystemConfigTemplate();

            if ($this->supportedSystemConfigTemplate !== null) {
                $configSettings = [];

                foreach ($this->supportedSystemConfigTemplate as $key => $Settings) {
                    $configSettings[$key] = isset($Settings->default) ? (string) $Settings->default : '';
                }
                $this->configSettings = (object) $configSettings;

                $this->main['configSettings'] = json_encode($this->configSettings);
            }
        } else {
            $this->main['configSettings'] = json_encode($this->configSettings);
        }
    } // _onBeforeValidation()


    /**
     * Überschreibt die gleichnamige Methode der Base-Klasse
     *
     * @version     1.0.0 / 2025-05-25
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function mount(): void
    {
        parent::mount();

        $this->_initSupportedSystems();
        $this->_initTenant();

        if ($this->exists === true) {
            $this->_initConfigSettings();
            $this->_initSupportedSystemConfigTemplate();

            if ($this->supportedSystemConfigTemplate === null) {
                $this->tabs['config-settings']['active'] = false;
            }
        } else {
            $this->tabs['config-settings']['active'] = false;
        }
    } // mount()


/* +++ CLASS METHODS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


} // class Form extends BaseForm {}
