<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/25/16
 * Time: 10:00 PM
 */

namespace IMATHUZH\Qfq\Core;

use IMATHUZH\Qfq\Core\Exception\RedirectResponse;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Helper\Token;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;


/**
 * Class BuildFormBootstrap
 * @package qfq
 */
class BuildFormBootstrap extends AbstractBuildForm {

    private $isFirstPill;

    private $tokenGiven = array();

    /**
     * @param array $formSpec
     * @param array $feSpecAction
     * @param array $feSpecNative
     * @param array $db Array of 'Database' instances
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $db) {
        parent::__construct($formSpec, $feSpecAction, $feSpecNative, $db);
        $this->isFirstPill = true;
    }

    /**
     *
     */
    public function fillWrap(): void {

//        $this->wrap[WRAP_SETUP_OUTER][WRAP_SETUP_START] = '<div class="tab-content">';
//        $this->wrap[WRAP_SETUP_OUTER][WRAP_SETUP_END] = '</div>';

        $this->wrap[WRAP_SETUP_TITLE][WRAP_SETUP_START] = "<div class='row'><div class='col-md-12'><h1>";
        $this->wrap[WRAP_SETUP_TITLE][WRAP_SETUP_END] = "</h1></div></div>";

        // Element: Label + Input + Note
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] = "form-group clearfix";
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_START] = "<div class='" . $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] . "'>";
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] = "</div>";

        $this->wrap[WRAP_SETUP_SUBRECORD][WRAP_SETUP_START] = "<div class='col-md-12'>";
        $this->wrap[WRAP_SETUP_SUBRECORD][WRAP_SETUP_END] = "</div>";

        $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START] = "";
        $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END] = "";

        $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] = "";
        $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END] = "";

//        $this->feDivClass['radio'] = 'radio';
//        $this->feDivClass['checkbox'] = 'checkbox';
    }

    /**
     * @param string $addClass
     *
     * @return string
     * @throws \CodeException
     */
    public function getRowOpenTag($addClass = ''): string {
        $class = Support::doAttribute('class', [$this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS], $addClass]);

        return "<div $class>";

    }

    /**
     * Fill the BS wrapper for Label/Input/Note.
     * For legacy reasons, $label/$input/$note might be a number (0-12) or the bs column classes ('col-md-12 col-lg9')
     *
     * @param $label
     * @param $input
     * @param $note
     */
    public function fillWrapLabelInputNote($label, $input, $note): void {

        $label = is_numeric($label) ? ($label == 0 ? 'hidden' : 'col-md-' . $label ) : $label;
        $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START] = "<div class='$label qfq-label'>";
        $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END] = "</div>";

        $input = is_numeric($input) ? ($input == 0 ? 'hidden' : 'col-md-' . $input) : $input;
        $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START] = "<div class='$input'>";
        $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END] = "</div>";

        $note = is_numeric($note) ? ($note == 0 ? 'hidden' : 'col-md-' . $note) : $note;
        $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START] = "<div class='$note qfq-note'>";
        $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END] = "</div>";

    }

    /**
     * @return string
     */
    public function getProcessFilter(): string {
        return FORM_ELEMENTS_NATIVE_SUBRECORD;
    }

    /**
     * @return string
     */
    public function doSubrecords(): string {
        return '';
    }

    /**
     * @param string $mode
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function head($mode = FORM_LOAD): string {
        $html = '';
        $title = '';

        $html .= '<div ' . Support::doAttribute('class', $this->formSpec[F_CLASS], true) . '>'; // main <div class=...> around everything, Whole FORM; class="container" or class="container-fluid"

        $button = $this->buildButtons(F_BTN_TOP);
        // Show title / frame only if there is a title given.
        if (trim($this->formSpec[F_TITLE]) != '') {
            $classTitle = isset($this->formSpec[F_CLASS_TITLE]) ? $this->formSpec[F_CLASS_TITLE] : "qfq-form-title";

            $buttons = null;

            // Form.parameter: btnPreviousNextSql
            if (isset($this->formSpec[F_BTN_PREVIOUS_NEXT_SQL])) {
                $btnPreviousNextSqlArr = $this->formSpec[F_BTN_PREVIOUS_NEXT_SQL] ?? '';
                $btnPreviousNextLoopFlag = $this->formSpec[F_BTN_PREVIOUS_NEXT_LOOP] ?? false;
                $btnPreviousNextLoopFlag = $btnPreviousNextLoopFlag === '' || $btnPreviousNextLoopFlag === '1';
                $btnPreviousNextWrapHtml = $this->formSpec[F_BTN_PREVIOUS_NEXT_WRAP] ?? '<span class="pull-right"><div class="btn-group" role="group">';
                $currentRecordId = $this->store::getVar('id', STORE_RECORD);
                $buttons = $this->buildPreviousNextButtons($btnPreviousNextSqlArr, $currentRecordId, $btnPreviousNextLoopFlag);
                $buttons = Support::wrapTag($btnPreviousNextWrapHtml, $buttons);
            }

            $title = Support::wrapTag('<div class="row"><div class="col-md-12">', Support::wrapTag('<div class="' . $classTitle . '">', $this->formSpec[F_TITLE] . $buttons));
        }

        $html .= $button . $title;

        $dummy = array();
        $pill = $this->buildPillNavigation($mode, OnArray::filter($this->feSpecNative, FE_TYPE, FE_TYPE_PILL), $dummy);
        $html .= Support::wrapTag('<div class="row">', $pill);

        $html .= $this->getFormTag();

        $class = ['tab-content', $this->formSpec[F_CLASS_BODY]];
        if ($pill == '') {
            $class[] = 'col-md-12';
            //   $class[] = 'qfq-form-body'; // Make an outline on form body

            if ($title == '') {
                $class[] = 'qfq-form-no-title';
            }
        }
        $class[] = 'qfq-form-body';
        $html .= "<div " . Support::doAttribute('class', $class) . ">";

        return $html;
    }

    /**
     * Creates a Checkbox, which toggles 'hide'/'unhide' via JS, on all elements with class= CLASS_FORM_ELEMENT_EDIT.
     *
     * @return string - the rendered Checkbox
     */
    private function buildShowEditFormElementCheckbox(): string {
        // EditFormElement Icons
        if ($this->showDebugInfoFlag) {
            $js = '$(".' . CLASS_FORM_ELEMENT_EDIT . '").toggleClass("hidden")';
            $element = "<input type='checkbox' onchange='" . $js . "'>" .
                Support::wrapTag("<span title='Toggle: Edit form element icons' class='" . GLYPH_ICON . ' ' . GLYPH_ICON_TASKS . "'>", '');
            $element = Support::wrapTag('<label class="btn btn-default navbar-btn">', $element);

            return Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $element);
        }
    }

    /**
     * Creates a button to open 'CopyForm' with the current form as source.
     *
     * @return string - the rendered button
     * @throws \CodeException
     * @throws \UserFormException
     */
    private function buildButtonCopyForm(): string {

        if ($this->showDebugInfoFlag) {
            // Show copy icon only on form 'form' and only if there is a form loaded (id>0)
            if ($this->formSpec[COLUMN_ID] != 1002) {
                return '';
            }
            // current loaded form.
            $formId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);

            $queryStringArray = [
                'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
                'form' => 'copyForm',
                'r' => 0,
                'idSrc' => $formId,
            ];
            $queryString = Support::arrayToQueryString($queryStringArray);
            $sip = $this->store->getSipInstance();
            $url = $sip->queryStringToSip($queryString);

            $toolTip = "Duplicate form" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
            $status = ($formId == 0) ? 'disabled' : '';

            return $this->buildButtonAnchor($url, 'form-view-' . $this->formSpec[F_ID], '', $toolTip, GLYPH_ICON_DUPLICATE, $status, 'btn btn-default navbar-btn');

        }
    }

    /**
     * Builds a button to open the formLog. The formLog appears on the same page as the form, but the form is not rendered and the log is shown.
     *
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function buildLogForm(): string {

        if (T3Handler::useSlugsInsteadOfPageAlias()) {
            $pageSlug = $this->store::getVar(TYPO3_PAGE_SLUG, STORE_TYPO3);
            $baseUrl = 'p:' . $pageSlug . '?form=' . $this->formSpec[F_NAME];
        } else {
            $pageAlias = $this->store::getVar(TYPO3_PAGE_ALIAS, STORE_TYPO3);
            $baseUrl = 'p:' . $pageAlias . '&form=' . $this->formSpec[F_NAME];
        }

        $baseMisc = '|b|G:glyphicon-bell|s|c:btn btn-default navbar-btn';
        $baseTooltip = '|o:Set form in debugmode and show actions from ';

        $stateAll = ($this->formSpec[FORM_LOG_FILE_ALL] == '') ? '' : ' btn-warning';
        $stateSession = ($this->formSpec[FORM_LOG_FILE_SESSION] == '') ? '' : ' btn-warning';

        $formLogAll = $this->link->renderLink($baseUrl . "&" . FORM_LOG_MODE . "=" . FORM_LOG_ALL . $baseMisc . $stateAll . $baseTooltip . 'all user');
        $formLogSession = $this->link->renderLink($baseUrl . "&" . FORM_LOG_MODE . "=" . FORM_LOG_SESSION . $baseMisc . $stateSession . $baseTooltip . 'current user');

        return $formLogAll . $formLogSession;
    }

    /**
     * Create a link to open current form loaded in FormEditor
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    private function buildViewForm(): string {

        if ($this->showDebugInfoFlag) {
            $value = $this->store->getVar(SYSTEM_FORM_VIEW_BUTTON_DEFAULT, STORE_DEFAULT);
            $form = false;
            $url = '';
            $status = '';
            $requiredNew = '';

            switch ($this->formSpec[F_NAME]) {
                case 'form':
                    $form = $this->store->getVar(F_NAME, STORE_RECORD);
                    break;
                case 'formElement':
                    if (false !== ($formId = $this->store->getVar(FE_FORM_ID, STORE_SIP . STORE_RECORD))) {
                        $row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `f`.`name` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1);
                        $rowRequiredNew = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `f`.`requiredParameterNew` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1);
                        $requiredNew = current($rowRequiredNew);
                        $form = current($row);
                    }
                    break;
                default:
                    return '';
            }

            if ($form === false) {
                $toolTip = "Form not 'form' or 'formElement'";
                $status = 'disabled';
                $value .= '|:o' . $toolTip . '|A:' . $status;
            } else {
                if ($requiredNew === '') {
                    $requiredNew = $this->store->getVar(F_REQUIRED_PARAMETER_NEW, STORE_RECORD . STORE_EMPTY);
                }
                if (trim($requiredNew) !== '') {
                    $toolTip = "Form has 'required new' parameters and therefore cannot be previewed.";
                    $status = 'disabled';
                    $value .= '|:o' . $toolTip . '|A:' . $status;

                } else {
                    $queryStringArray = [
                        'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
                        'form' => $form,
                        'r' => 0,
                    ];
                    $queryString = Support::arrayToQueryString($queryStringArray);
                    $sip = $this->store->getSipInstance();
                    $url = $sip->queryStringToSip($queryString);

                    $toolTip = "View current form with r=0" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
                    $value .= '|o:' . $toolTip;

                }
            }
            $oldFormViewButton = $this->buildButtonAnchor($url, 'form-view-' . $this->formSpec[F_ID], '', $toolTip, GLYPH_ICON_VIEW, $status, 'btn btn-default navbar-btn');

            $formViewButton = $this->link->renderLink($value);

            return $formViewButton;

        }
    }

    /**
     * @return array[]
     */
    private function getDefaultButtons(): array {

        return [
            FORM_BUTTON_FORM_VIEW => [
                'method' => 'buildViewForm',
            ],
            FORM_BUTTON_FORM_ELEMENT_EDIT => [
                'method' => 'buildShowEditFormElementCheckbox',
            ],
            FORM_BUTTON_FORM_EDIT => [
                'method' => 'buildFormEditButton',
            ],
            FORM_BUTTON_SAVE => [
                'method' => 'buildSaveButton',
                'attribute' => 'data-class-on-change="btn-info alert-info"',
            ],
            FORM_BUTTON_CLOSE => [
                'method' => 'buildCloseButton',
            ],
            FORM_BUTTON_DELETE => [
                'method' => 'buildDeleteButton',
            ],
            FORM_BUTTON_NEW => [
                'method' => 'buildNewButton',
            ],
            FORM_BUTTON_ISSUE => [
                'method' => 'buildNoteButton',
            ],
            FORM_BUTTON_HISTORY => [
                'method' => 'buildHistoryButton',
            ]
        ];

    }

    /**
     * Build note button in form.
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     *
     */
    private function buildNoteButton(): string {

        $recordId = $this->recordId;

        // NoteItem enabled?
        if (!Support::findInSet(FORM_BUTTON_ISSUE, $this->formSpec[F_SHOW_BUTTON])) {
            return '';
        }

        $formId = $this->formSpec[ISSUE_COLUMN_ID];
        $formName = $this->store->getVar(FORM_NAME_FORM, STORE_SIP);

        $xId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);

        // Get pIdUser. 'User Switch' should be handled in STORE_SYSTEM > fillStoreSystemBySqlRowVariable
        $pIdUser = $this->store->getVar(SYSTEM_PID_USER, STORE_SYSTEM . STORE_ZERO);

        // TODO JH: Clean refactor from NoteItem to Issue
        $sql = "SELECT COUNT(*) AS cnt FROM " . ISSUE_TABLE . " AS nf WHERE nf.formId =" . $formId . " AND nf.xId=" . $recordId
        //    . " AND (nf.pIdCreator = " . $pIdUser . " OR nf.access= 'all read' OR nf.access = 'all write')" . " GROUP BY nf.xId";
            . " AND (nf.pIdCreator = " . $pIdUser . " OR nf.access= 'public')" . " GROUP BY nf.xId";
        // Count the number of notes related to the form and record, accessible by the user or publicly ('all')
        $sqlResult = $this->dbArray[$this->dbIndexData]->sql($sql, ROW_EXPECT_0_1);
        $noteTotal = $sqlResult['cnt'] ?? 0;
        $disabled = ($xId == '0' || $formName == ISSUE_NAME) ? HTML_ATTR_HIDDEN : '';

        // TODO JH: Clean refactor from NoteItem to Issue
        // Query to find notes that are not marked as done
        $sqlResult = $this->dbArray[$this->dbIndexData]->sql("SELECT COUNT(*) AS cnt FROM " . ISSUE_TABLE .
            " AS nf WHERE nf.formId = " . $formId . " AND nf.xId = " . $recordId . " /* AND nf.isDone = 'no' AND nf.hasToolbar = 'yes' */ GROUP BY formId",
            ROW_EXPECT_0_1);
        $noteTotalOpen = $sqlResult['cnt'] ?? 0;

        // Tooltip note button
        $toolTip = 'Notes';
        // Set the badge class based on whether there are notes not done.
        $badgeClass = 'badge';
        $style = '';
        if ($noteTotalOpen > 0 && $noteTotal != 0) {
            $badgeClass = 'qfq-badge qfq-badge-warning';
            $toolTip .= " (open: $noteTotalOpen, total: $noteTotal)";
        }

        $msg = ($noteTotalOpen > 0) ? $noteTotalOpen . ' / ' . $noteTotal : $noteTotal;
        $badge = "<span class='$badgeClass' $style>$msg</span>";

        // Debug Info
        if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
            $toolTip .= PHP_EOL . '--' . PHP_EOL . 'xId: ' . $recordId . PHP_EOL . 'formName:' . $formName;
        }

        $pageSlug = $this->store->getVar(TYPO3_PAGE_SLUG, STORE_TYPO3);
        $formTitle = $this->formSpec[COLUMN_TITLE];

        // Construct the URL for the note button link
        $noteUrl = 'p:' . $pageSlug . '?form=' . ISSUE_NAME . '&formId=' . $formId . '&r=0&xId=' . $xId . '&formName=' . $formName . '&formTitle=' . $formTitle . '&type=1|s|r:7';


        $noteUrl = $this->link->renderLink($noteUrl);

        $targetForm = $formName . '-' . $recordId;

        // define the new window size
        $onClickJs = "window.open('$noteUrl', '$targetForm' , 'scrollbars=no,resizable=yes,width=800,height=950'); return false;";

        // Build the anchor tag for the button with all the properties set
        $buttonNote = $this->buildButtonAnchor($noteUrl, NOTE_ITEM_BUTTON_HTML_ID, $this->formSpec[F_NOTE_BUTTON_TEXT], $toolTip, GLYPH_ICON_PAPERCLIP, $disabled, 'btn btn-default navbar-btn', $badge, $onClickJs);


        return Support::wrapTag('<div class="btn-group" role="group">', $buttonNote, true);
    }


    /**
     * Builds the button panels for either the top or footer section of a form, based on the specified position.
     * It dynamically constructs buttons based on predefined configurations and custom buttons.
     * The method also determines whether to place button groups on the right or left side based on the form's configuration.
     *
     * Simulate Submit Button: http://www.javascript-coder.com/javascript-form/javascript-form-submit.phtml
     *
     * @param $position
     * @return string
     */
    private function buildButtons($position): string {
        // Initialize an array to hold the default buttons
        $defaultButtons = [];

        // Iterate through each default button configuration
        foreach ($this->getDefaultButtons() as $key => $config) {
            // Check if the config is an array and has a 'method' key
            if (is_array($config) && isset($config['method'])) {
                // Prepare the callable method reference
                $methodCallable = [$this, $config['method']];

                // Check if the method is callable (exists and is accessible)
                if (is_callable($methodCallable)) {
                    // Dynamically invoke the method to get the button HTML
                    $buttonHtml = call_user_func($methodCallable);

                    // Store the resulting HTML in the default buttons array
                    $defaultButtons[$key] = $buttonHtml;
                }
            }
        }

        // Retrieve any custom buttons that may have been defined
        $customButtons = $this->getCustomButtons();
        // Merge the default and custom buttons
        $buttons = array_merge($defaultButtons, $customButtons);

        // Initialize flags to determine where to place the button groups
        $useRight = $useLeft = false;
        $rightHtml = $leftHtml = ''; // Initialize variables for the button group HTML


        $defaultRightCondition = !array_key_exists(F_BTN_TOP_LEFT, $this->formSpec) &&
            !array_key_exists(F_BTN_FOOTER_RIGHT, $this->formSpec) &&
            !array_key_exists(F_BTN_FOOTER_LEFT, $this->formSpec);

        // Handling for the top section of the form
        if ($position === F_BTN_TOP) {
            // Check if there's a specific order defined for the right button group at the top
            $useRightOrder = isset($this->formSpec[F_BTN_TOP_RIGHT_ORDER]) && $this->formSpec[F_BTN_TOP_RIGHT_ORDER] !== '';

            $useRight = $useRightOrder || array_key_exists(F_BTN_TOP_RIGHT, $this->formSpec) || $defaultRightCondition;

            // Similarly, check for the left button group order at the top
            $useLeftOrder = isset($this->formSpec[F_BTN_TOP_LEFT_ORDER]) && $this->formSpec[F_BTN_TOP_LEFT_ORDER] !== '';
            $useLeft = $useLeftOrder || array_key_exists(F_BTN_TOP_LEFT, $this->formSpec);
        } // Handling for the footer section of the form
        elseif ($position === F_BTN_FOOTER) {
            // Check for the right and left button group orders in the footer section
            $useRightOrder = isset($this->formSpec[F_BTN_FOOTER_RIGHT_ORDER]) && $this->formSpec[F_BTN_FOOTER_RIGHT_ORDER] !== '';
            $useRight = $useRightOrder || array_key_exists(F_BTN_FOOTER_RIGHT, $this->formSpec);

            $useLeftOrder = isset($this->formSpec[F_BTN_FOOTER_LEFT_ORDER]) && $this->formSpec[F_BTN_FOOTER_LEFT_ORDER] !== '';
            $useLeft = $useLeftOrder || array_key_exists(F_BTN_FOOTER_LEFT, $this->formSpec);
        }

        // Build the HTML for the right and left button groups based on the configuration
        if ($useRight) {
            $rightKey = $position === F_BTN_TOP ? F_BTN_TOP_RIGHT : F_BTN_FOOTER_RIGHT;
            $rightHtml = $this->buildButtonGroup($rightKey, $buttons);
        }
        if ($useLeft) {
            $leftKey = $position === F_BTN_TOP ? F_BTN_TOP_LEFT : F_BTN_FOOTER_LEFT;
            $leftHtml = $this->buildButtonGroup($leftKey, $buttons);
        }

        // Wrap and assemble the left and right button groups into a single HTML structure
        $html = $this->wrapButtonGroups($leftHtml, $rightHtml, $position);

        // Return the final HTML
        return $html;
    }


    /**
     * Wraps the  left and right button groups with designated wrappers and combines them into a single HTML structure.
     * The wrapping and combination are based on the specified position, which can be either at the top of the form (F_BTN_TOP)
     * or at the footer (F_BTN_FOOTER). This method first attempts to use custom wrappers defined in `formSpec`; if none are
     * specified, it falls back to system defaults or predefined defaults.
     *
     * @param string $leftHtml for the left button group.
     * @param string $rightHtml for the right button group.
     * @param string $position The position of the button groups within the form, which determines the set of wrappers to use.
     * @return string The combined HTML string of the left and right button groups, each wrapped with its respective wrapper and then combined within an outer wrapper.
     */
    private function wrapButtonGroups($leftHtml, $rightHtml, $position): string {
        if ($position == F_BTN_TOP) {
            // Fetch wrapping custom or use default html
            $btnTopLeftWrapSystem = $this->store->getVar(F_BTN_TOP_LEFT_WRAP, STORE_SYSTEM);
            $btnTopLeftWrap = $this->formSpec[F_BTN_TOP_LEFT_WRAP] ?? ($btnTopLeftWrapSystem !== '' && $btnTopLeftWrapSystem !== false ? $btnTopLeftWrapSystem : $this->store->getVar(F_BTN_TOP_LEFT_WRAP, STORE_DEFAULT));

            $btnTopRightWrapSystem = $this->store->getVar(F_BTN_TOP_RIGHT_WRAP, STORE_SYSTEM);
            $btnTopRightWrap = $this->formSpec[F_BTN_TOP_RIGHT_WRAP] ?? ($btnTopRightWrapSystem !== '' && $btnTopRightWrapSystem !== false ? $btnTopRightWrapSystem : $this->store->getVar(F_BTN_TOP_RIGHT_WRAP, STORE_DEFAULT));

            // Conditionally wrap the left and right content with the respective wrappers
            $leftWrapped = $leftHtml ? Support::wrapTag($btnTopLeftWrap, $leftHtml, true) : '';
            $rightWrapped = $rightHtml ? Support::wrapTag($btnTopRightWrap, $rightHtml, true) : '';

            // Combine left and right wrapped content
            $btnTopWrapSystem = $this->store->getVar(F_BTN_TOP_WRAP, STORE_SYSTEM);
            $btnTopWrap = $this->formSpec[F_BTN_TOP_WRAP] ?? ($btnTopWrapSystem !== '' && $btnTopWrapSystem !== false ? $btnTopWrapSystem : $this->store->getVar(F_BTN_TOP_WRAP, STORE_DEFAULT));
            $html = Support::wrapTag($btnTopWrap, $leftWrapped . $rightWrapped, true);

        } // build button for footer
        elseif ($position == F_BTN_FOOTER) {
            // Fetch wrapping configurations or use default values in default store or system store
            $btnFooterLeftWrapSystem = $this->store->getVar(F_BTN_FOOTER_LEFT_WRAP, STORE_SYSTEM);
            $btnFooterLeftWrap = $this->formSpec[F_BTN_FOOTER_LEFT_WRAP] ?? ($btnFooterLeftWrapSystem !== '' && $btnFooterLeftWrapSystem !== false ? $btnFooterLeftWrapSystem : $this->store->getVar(F_BTN_FOOTER_LEFT_WRAP, STORE_DEFAULT));

            $btnFooterRightWrapSystem = $this->store->getVar(F_BTN_FOOTER_RIGHT_WRAP, STORE_SYSTEM);
            $btnFooterRightWrap = $this->formSpec[F_BTN_FOOTER_RIGHT_WRAP] ?? ($btnFooterRightWrapSystem !== '' && $btnFooterRightWrapSystem !== false ? $btnFooterRightWrapSystem : $this->store->getVar(F_BTN_FOOTER_RIGHT_WRAP, STORE_DEFAULT));

            // Conditionally wrap the left and right content with the respective wrappers
            $leftWrapped = $leftHtml ? Support::wrapTag($btnFooterLeftWrap, $leftHtml, true) : '';
            $rightWrapped = $rightHtml ? Support::wrapTag($btnFooterRightWrap, $rightHtml, true) : '';

            // Combine left and right wrapped content
            $btnFooterWrapSystem = $this->store->getVar(F_BTN_FOOTER_WRAP, STORE_SYSTEM);
            $btnFooterWrap = $this->formSpec[F_BTN_FOOTER_WRAP] ?? ($btnFooterWrapSystem !== '' && $btnFooterWrapSystem === true ? $btnFooterWrapSystem : $this->store->getVar(F_BTN_FOOTER_WRAP, STORE_DEFAULT));
            $html = Support::wrapTag($btnFooterWrap, $leftWrapped . $rightWrapped, true);
        }
        return $html;
    }


    /**
     * It looks into specific configurations for footer left, footer right, and top left button orders,
     * parsing each defined button order to extract individual button keys.
     *
     * @return array An array of unique button identifiers extracted from the form's configuration.
     */
    private function getAllButtonKeys(): array {
        $allKeys = [];

        // List of all possible button order keys from which to extract button identifiers
        $orderKeys = [
            F_BTN_FOOTER_LEFT_ORDER,
            F_BTN_FOOTER_RIGHT_ORDER,
            F_BTN_TOP_LEFT_ORDER,
        ];

        foreach ($orderKeys as $orderKey) {
            // Check if the order key is set in formSpec
            if (isset($this->formSpec[$orderKey])) {
                // Get the order string for the current key
                $order = $this->formSpec[$orderKey];

                // Split the order string by both ',' and '+' to treat each button separately
                $buttons = preg_split('/[+,]/', $order);

                // Merge the extracted buttons into the allKeys array
                $allKeys = array_merge($allKeys, $buttons);
            }
        }

        // Remove duplicate button identifiers to ensure each is only listed once
        $uniqueKeys = array_unique($allKeys);

        return $uniqueKeys; // Returns an array of unique button keys
    }

    /**
     * Adjusts the button order by removing specified buttons.
     * This function takes a string representing the default order of buttons and an array of buttons to remove.
     * It then reconstructs the order, omitting the specified buttons, while preserving the original grouping structure as much as possible.
     *
     * @param string $defaultOrder The original ordering of buttons as a string. Groups of buttons are separated by commas,
     *                             and individual buttons within those groups are separated by pluses.
     * @param array $removeButtons An array of button identifiers that should be removed from the $defaultOrder.
     * @return string The adjusted button order with specified buttons removed, maintaining the original groupings.
     */
    private function adjustButtonOrder($defaultOrder, $removeButtons): string {

        $orderArray = explode(',', $defaultOrder);

        $flattenedButtons = [];

        foreach ($orderArray as $group) {

            $groupButtons = explode('+', $group);

            $flattenedButtons = array_merge($flattenedButtons, $groupButtons);
        }

        // Remove the buttons specified in $removeButtons from the flattened array.
        $flattenedButtons = array_diff($flattenedButtons, $removeButtons);

        $updatedOrderArray = [];

        foreach ($orderArray as $group) {

            $groupButtons = explode('+', $group);

            $updatedGroup = array_intersect($groupButtons, $flattenedButtons);

            // If the updated group isn't empty, convert it back into a string and add it to the updated order array.
            if (!empty($updatedGroup)) {
                $updatedOrderArray[] = implode('+', $updatedGroup);
            }
        }

        // Convert the updated order array back into a string, with groups separated by commas, and return it.
        return implode(',', $updatedOrderArray);
    }


    /**
     * Build HTML for a group of buttons for either the left or right side.
     *
     * @param string $side Determines which side's button group to build, accepting values like 'btnTopLeft', 'btnTopRight' , btnFooterLeft, btnFooterRight.
     * @param array $buttons Contains all available buttons' HTML strings, indexed by button identifier.
     * @return string The final HTML string representing a group of buttons for the specified side.
     */
    private function buildButtonGroup($side, $buttons): string {
        // Construct the key used to retrieve the order of buttons for the specified side
        $orderKey = $side . "Order";

        // Fetch the default button order from the store, if available
        $defaultOrder = $this->store->getVar($orderKey, STORE_DEFAULT);
        // Fetch system-specific button order, if it has been set
        $defaultOrderSystem = $this->store->getVar($orderKey, STORE_SYSTEM);

        // Decide which button order to use: system-specific if set, otherwise the default
        $btnOrder = ($defaultOrderSystem !== false && $defaultOrderSystem !== '') ? $defaultOrderSystem : $defaultOrder;

        // If a custom order is specified in the form's configuration, override the button order with it
        if (isset($this->formSpec[$orderKey]) && !empty($this->formSpec[$orderKey])) {
            $btnOrder = $this->formSpec[$orderKey];
        }

        // Special case: if certain conditions are met and the current side is 'btnTopRight',
        // adjust the button order by removing certain keys (explained further below)
        if ((isset($this->formSpec[F_BTN_FOOTER_LEFT_ORDER]) || isset($this->formSpec[F_BTN_FOOTER_RIGHT_ORDER]) ||
                isset($this->formSpec[F_BTN_TOP_LEFT_ORDER])) && !isset($this->formSpec[F_BTN_TOP_RIGHT_ORDER]) &&
            F_BTN_TOP_RIGHT_ORDER === $orderKey) {
            // Get all button keys to determine which ones to remove
            $removeKeys = $this->getAllButtonKeys();
            // Adjust the button order by removing the specified keys
            $btnOrder = $this->adjustButtonOrder($defaultOrder, $removeKeys);
        }

        // Initialize the HTML string to be built
        $html = '';

        // Split the button order into groups separated by commas
        $btnGroups = explode(',', $btnOrder);

        // Iterate through each group of buttons
        foreach ($btnGroups as $group) {
            // Split the group into individual button identifiers separated by pluses
            $btnKeys = explode('+', $group);
            // Initialize a variable to hold the HTML for the current group
            $groupHtml = '';

            // Iterate through each button identifier in the current group
            foreach ($btnKeys as $key) {
                // If the key is numeric, treat it as a custom button identifier
                if (is_numeric($key)) {
                    $key = "$key";  // Ensure the key is a string
                }
                // If HTML for the current button exists, append it to the group's HTML
                if (!empty($buttons[$key])) {
                    $groupHtml .= $buttons[$key];
                }
            }

            // If the current group contains any buttons, wrap the group's HTML and append it to the final HTML string
            if (!empty($groupHtml)) {
                $html .= Support::wrapTag('<div class="btn-group" role="group">', $groupHtml, true);
            }
        }
        // Return the final HTML for the button group
        return $html;
    }

    /**
     * get custom buttons based on $this->formSpec with parameter.btnCustom
     *
     * @return array Array of custom buttons' HTML strings.
     */
    private function getCustomButtons(): array {
        $customButtons = [];
        $defaultButtons = $this->getDefaultButtons();

        foreach ($this->formSpec as $key => $value) {
            if (preg_match('/^btn(Custom)\[([^\]]+)\]$/', $key, $matches)) {
                $buttonKey = $matches[2];

                if (isset($defaultButtons[$buttonKey])) {
                    // Process with custom parameters for default buttons
                    $buttonConfig = $defaultButtons[$buttonKey];

                    if (is_array($buttonConfig) && isset($buttonConfig['attribute'])) {
                        // Split 'attribute' into individual attributes
                        $jsAttributes = explode(',', $buttonConfig['attribute']);
                        if (strpos($value, '|A:') === false) {
                            $value .= '|A:';
                        }
                        foreach ($jsAttributes as $attr) {
                            $attr = trim($attr);
                            if (!empty($attr)) {
                                $value .= ' ' . $attr;
                            }
                        }
                    }
                    $customButtons[$buttonKey] = $this->link->renderLink($value);

                } else {
                    // Truly custom buttons
                    $customButtons[$buttonKey] = $value;
                }
            }
        }

        return $customButtons;
    }


    /**
     * build Formedit button to edit the form
     *
     * @return string|void
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     *
     */
    public function buildFormEditButton(): string {
        // Button: FormEdit
        if ($this->showDebugInfoFlag) {
            // Fetch default parameters for the button
            $value = $this->store->getVar(SYSTEM_FORM_EDIT_BUTTON_DEFAULT, STORE_DEFAULT);
            $toolTip = "Edit form" . PHP_EOL . PHP_EOL . OnArray::toString($this->store->getStore(STORE_SIP), ' = ', PHP_EOL, "'");

            $value .= '|o: ' . $toolTip;

            // $url = $this->createFormEditorUrl(FORM_NAME_FORM, $this->formSpec[F_ID]);

            $formEditButton = $this->link->renderLink($value);

            //         $oldFormEditButton = $this->buildButtonAnchor($url, 'form-edit-button', '', $toolTip, GLYPH_ICON_TOOL, '', $this->formSpec[F_SAVE_BUTTON_CLASS]);

            return $formEditButton;
        }

    }

    /** Build button to open form history.
     * Deliver attributes for history view.
     *
     * @throws \CodeException
     * @throws RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     * @throws \DbException
     */
    private function buildHistoryButton(): string {

        // Check form parameter and qfq configuration if the history button should be shown
        $qfqConfigShowHistory = $this->store->getVar(SYSTEM_SHOW_HISTORY, STORE_SYSTEM);
        $formConfigShowHistory = $this->formSpec[F_SHOW_HISTORY] ?? false;
        $formShowHistoryTitle = $this->formSpec[F_SHOW_HISTORY_TITLE] ?? '';
        $formShowHistoryFeUserSet = $this->formSpec[F_SHOW_HISTORY_FE_USER_SET] ?? '';

        // Prioritize form parameter over qfq configuration
        if ($formConfigShowHistory === "0") {
            return '';
        }

        if ($qfqConfigShowHistory == 0 && !$formConfigShowHistory) {
            return '';
        }

        $tooltip = '';
        $recordId = $this->recordId;
        $formId = $this->formSpec[F_ID];
        $table = $this->formSpec[F_TABLE_NAME];

        if ($this->showDebugInfoFlag) {
            $tooltip .= PHP_EOL . "recordId: " . $recordId;
            $tooltip .= PHP_EOL . "formId: " . $formId;
            $tooltip .= PHP_EOL . "table: " . $table . PHP_EOL . PHP_EOL;
        }

        // Get last 11 FormSubmitLog entries for the current form
        $rows = $this->dbArray[$this->dbIndexData]->getFormHistoryRecords($table, $recordId, $formShowHistoryFeUserSet, 11);
        $more = count($rows) > 10 ? '...' : '';

        // if there are more than 10 entries remove the last one
        if (count($rows) > 10) {
            array_pop($rows);
        }

        // extract the sql result for tooltip looks like table: created | feUser | formName
        foreach ($rows as $row) {
            $tooltip .= implode(' \| ', $row) . PHP_EOL;
        }
        $tooltip .= $more;

        // Build history button
        $mode = RECORD_HISTORY_LOAD;
        $baseUrl = $this->store->getVar(SYSTEM_BASE_URL, STORE_SYSTEM);
        $tablesorterViewData[RECORD_HISTORY_DATA] = $this->evaluate->parse("{{'qfq-history-" . $formId . "' AS _tablesorter-view-saver}}");
        $tablesorterViewJson = htmlspecialchars(json_encode($tablesorterViewData, JSON_UNESCAPED_SLASHES));
        $sipText = 'r:8|s|U:&formId=' . $formId . '&r=' . $recordId . '&mode=' . $mode . '&table=' . $table . '&showHistoryFeUserSet=' . $formShowHistoryFeUserSet . '|';
        $sipData = $this->link->renderLink($sipText);
        $value = $this->store->getVar(SYSTEM_FORM_HISTORY_BUTTON_DEFAULT, STORE_DEFAULT);
        $value .= '|A:data-sip="' . $sipData . '" data-base-url="' . $baseUrl
            . '" data-tablesorter-view-json = "' . $tablesorterViewJson
            . '" data-history-title="' . $formShowHistoryTitle
            . '"|t:<i style="font-size:12pt;" class="fas fa-history"></i>|o: ' . $tooltip;

        return $this->link->renderLink($value);
    }

    /**
     * @param $mode  top or footer
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function buildSaveButton($mode = ''): string {
        $recordId = $this->recordId;
        // Button: Save
        if (Support::findInSet(FORM_BUTTON_SAVE, $this->formSpec[F_SHOW_BUTTON]) && $this->formSpec[F_SUBMIT_BUTTON_TEXT] === '') {
            $toolTip = $this->formSpec[F_SAVE_BUTTON_TOOLTIP];

            if ($toolTip == 'Save') {

                if ($recordId == 0) {
                    $toolTip .= PHP_EOL . 'Create new record';
                } else {
                    $toolTip .= PHP_EOL . 'Record id: ' . $recordId;
                    $toolTip .= PHP_EOL . 'Created: ' . $this->store->getVar('created', STORE_RECORD . STORE_EMPTY);;
                    $toolTip .= PHP_EOL . 'Modified: ' . $this->store->getVar('modified', STORE_RECORD . STORE_EMPTY);;
                }
            }
            // In debugMode every button link should show the information behind the SIP.
            if ($this->showDebugInfoFlag) {
                $toolTip .= PHP_EOL . "Table: " . $this->formSpec[F_TABLE_NAME];
            }
            if (!empty($this->formSpec[F_SAVE_BUTTON_TEXT]) || !empty($this->formSpec[F_SAVE_BUTTON_GLYPH_ICON]) || $this->formSpec[F_SAVE_BUTTON_CLASS] != 'btn btn-default navbar-btn') {
                $oldSaveButton = $this->buildButtonCode('save-button', $this->formSpec[F_SAVE_BUTTON_TEXT], $toolTip,
                    $this->formSpec[F_SAVE_BUTTON_GLYPH_ICON], '', $this->formSpec[F_BUTTON_ON_CHANGE_CLASS], $this->formSpec[F_SAVE_BUTTON_CLASS]);

                return $oldSaveButton;
            } else {
                $valueSystem = $this->store->getVar(SYSTEM_SAVE_BUTTON_DEFAULT, STORE_SYSTEM);
                $value = ($valueSystem !== false && $valueSystem !== '') ? $valueSystem : $this->store->getVar(SYSTEM_SAVE_BUTTON_DEFAULT, STORE_DEFAULT);
                $value .= '|' . TOKEN_TOOL_TIP . ':' . $toolTip;
                $value .= '|A:data-class-on-change="btn-info alert-info"';
                $saveButton = $this->link->renderLink($value);
                return $saveButton;
            }
        } elseif ($mode === F_BTN_FOOTER) {
            $html = '';
            // Default setzen:
            $this->fillWrapLabelInputNote($this->formSpec[F_BS_LABEL_COLUMNS], $this->formSpec[F_BS_INPUT_COLUMNS], $this->formSpec[F_BS_NOTE_COLUMNS]);

            $buttonText = $this->formSpec[F_SUBMIT_BUTTON_TEXT];

            $htmlElement = $this->buildButtonCode('save-button', $buttonText, $this->formSpec[F_SUBMIT_BUTTON_TOOLTIP],
                $this->formSpec[F_SUBMIT_BUTTON_GLYPH_ICON], '', $this->formSpec[F_BUTTON_ON_CHANGE_CLASS],
                $this->formSpec[F_SUBMIT_BUTTON_CLASS]);

            //New save button style if save button text is given
            $newButtonStyle = '<div class="row"><div class="col-md-12" style="height: 45px; padding-top: 8px;">';
            $lastDiv = '</div></div>';

            //finish elements and close tag-form befour wrapping save button
            $html = $this->wrapItem(WRAP_SETUP_ELEMENT, $html);

            $html .= $newButtonStyle;
            $html .= $this->wrapItem(WRAP_SETUP_LABEL, '');
            $html .= $this->wrapItem(WRAP_SETUP_INPUT, $htmlElement);
            $html .= $this->wrapItem(WRAP_SETUP_NOTE, '');
            $html .= $lastDiv;

            return $html;
        }
        return '';
    }

    /**
     * build close button
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function buildCloseButton(): string {
        // Button: Close
        if (Support::findInSet(FORM_BUTTON_CLOSE, $this->formSpec[F_SHOW_BUTTON])) {

            $tooltip = empty($this->formSpec[F_CLOSE_BUTTON_TOOLTIP]) ? 'Close' : $this->formSpec[F_CLOSE_BUTTON_TOOLTIP];


            if (empty($this->formSpec[F_CLOSE_BUTTON_TEXT]) || empty($this->formSpec[F_CLOSE_BUTTON_GLYPH_ICON]) || $this->formSpec[F_CLOSE_BUTTON_CLASS] === 'btn btn-default navbar-btn') {
                $valueSystem = $this->store->getVar(SYSTEM_CLOSE_BUTTON_DEFAULT, STORE_SYSTEM);
                $value = ($valueSystem !== false && $valueSystem !== '') ? $valueSystem : $this->store->getVar(SYSTEM_CLOSE_BUTTON_DEFAULT, STORE_DEFAULT);
                $value .= '|o:' . $tooltip;
                $closeButton = $this->link->renderLink($value);
                return $closeButton;

            } else {
                $oldCloseButton = $this->buildButtonCode('close-button', $this->formSpec[F_CLOSE_BUTTON_TEXT],
                    $tooltip,
                    $this->formSpec[F_CLOSE_BUTTON_GLYPH_ICON], '', '', $this->formSpec[F_CLOSE_BUTTON_CLASS]);
                return $oldCloseButton;
            }

        }
        return '';
    }

    /**
     * build delete button
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     *
     */
    private function buildDeleteButton(): string {
        $recordId = $this->recordId;
        // Button: Delete
        if (Support::findInSet(FORM_BUTTON_DELETE, $this->formSpec[F_SHOW_BUTTON])) {
            $toolTip = $this->formSpec[F_DELETE_BUTTON_TOOLTIP];


            if ($this->showDebugInfoFlag && $recordId > 0) {
                $toolTip .= PHP_EOL . "form = '" . $this->formSpec[F_FINAL_DELETE_FORM] . "'" . PHP_EOL . "r = '" . $recordId . "'";
            }


            if (empty($this->formSpec[F_DELETE_BUTTON_TEXT]) || empty($this->formSpec[F_DELETE_BUTTON_GLYPH_ICON]) || ($this->formSpec[F_DELETE_BUTTON_CLASS]) === 'btn btn-default navbar-btn') {
                $valueSystem = $this->store->getVar(SYSTEM_DELETE_BUTTON_DEFAULT, STORE_SYSTEM);
                $value = ($valueSystem !== false && $valueSystem !== '') ? $valueSystem : $this->store->getVar(SYSTEM_DELETE_BUTTON_DEFAULT, STORE_DEFAULT);
                $disabled = ($recordId > 0) ? '' : '|A: disabled';
                $value .= $disabled . '|o:' . $toolTip;
                return $this->link->renderLink($value);
            } else {
                $disabled = ($recordId > 0) ? '' : 'disabled';
                return $this->buildButtonCode('delete-button', $this->formSpec[F_DELETE_BUTTON_TEXT], $toolTip,
                    $this->formSpec[F_DELETE_BUTTON_GLYPH_ICON], $disabled, '', $this->formSpec[F_DELETE_BUTTON_CLASS]);
            }

        }
        return '';
    }

    /**
     * build new Button
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     *
     */
    private function buildNewButton(): string {
        // Button: New
        if (Support::findInSet(FORM_BUTTON_NEW, $this->formSpec[F_SHOW_BUTTON])) {

            $toolTip = empty($this->formSpec[F_NEW_BUTTON_TOOLTIP]) ? 'New' : $this->formSpec[F_NEW_BUTTON_TOOLTIP];

            if (empty($this->formSpec[F_NEW_BUTTON_TEXT]) || empty($this->formSpec[F_NEW_BUTTON_GLYPH_ICON]) || $this->formSpec[F_NEW_BUTTON_CLASS] === 'btn btn-default navbar-btn') {
                $valueSystem = $this->store->getVar(SYSTEM_NEW_BUTTON_DEFAULT, STORE_SYSTEM);
                $value = ($valueSystem !== false && $valueSystem !== '') ? $valueSystem : $this->store->getVar(SYSTEM_NEW_BUTTON_DEFAULT, STORE_DEFAULT);
                $value .= '|o:' . $toolTip;
                $newButton = $this->link->renderLink($value);
                return $newButton;
            } else {
                $url = $this->deriveNewRecordUrlFromExistingSip($toolTip);
                // deprecated
                $oldNewButton = $this->buildButtonAnchor($url, 'form-new-button', $this->formSpec[F_NEW_BUTTON_TEXT],
                    $this->formSpec[F_NEW_BUTTON_TOOLTIP], $this->formSpec[F_NEW_BUTTON_GLYPH_ICON], '', $this->formSpec[F_NEW_BUTTON_CLASS]);
                return $oldNewButton;
            }

        }
        return '';
    }

    /**
     * Generic function to create a button with a given $buttonHtmlId, $url, $title, $icon (=glyph), $disabled
     *
     * @param string $url
     * @param string $buttonHtmlId
     * @param string $text
     * @param string $toolTip
     * @param string $icon
     * @param string $disabled
     * @param string $class
     *
     * @return string
     * @throws \CodeException
     */
    private function buildButtonAnchor($url, $buttonHtmlId, $text, $toolTip, $icon, $disabled = '', $class = '', $badge = '', $onClickJs = ''): string {


        $element = $text;

        // If there's an icon, prepend it to the element content
        if ($icon !== '') {
            $iconSpan = "<span " . Support::doAttribute('class', "glyphicon $icon") . "></span>";
            $element = $iconSpan . ' ' . $element;
        }

//        if ($icon === '') {
//            $element = $text;
//        } else {
//            $element = Support::wrapTag("<span " . Support::doAttribute('class', "glyphicon $icon") . ">", $text);
//        }

        // If there's a badge, append it to the element content

        if ($badge !== '') {
            $element = $element . ' ' . $badge;
        }

        $attribute = Support::doAttribute('href', $url);
        $attribute .= Support::doAttribute('id', $buttonHtmlId);
        $attribute .= Support::doAttribute('class', "$class $disabled");
        $attribute .= Support::doAttribute('title', $toolTip);

        // Conditionally add the `onclick` attribute if $onClickJs is not empty
        if (!empty($onClickJs)) {
            $attribute .= Support::doAttribute('onclick', $onClickJs);
        }

        // disabled links do not show tooltips -> use a span
        $wrapTag = $disabled == 'disabled' ? 'span' : 'a';

        return Support::wrapTag("<$wrapTag $attribute>", $element);
    }

    /**
     * Builds the BS-pills on top of a form.
     *
     * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
     * @param array $pillArray
     *
     * @param array $json
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function buildPillNavigation($mode, array $pillArray, array &$json): string {
        $pillButton = '';
        $pillDropdown = '';
        $htmlDropdown = '';

        if ($pillArray == null) {
            return '';
        }

        $maxVisiblePill = (isset($this->formSpec['maxVisiblePill']) && $this->formSpec['maxVisiblePill'] !== '') ? $this->formSpec['maxVisiblePill'] : 1000;

        $parameterLanguageFieldName = $this->store->getVar(SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME, STORE_SYSTEM);

        // Iterate over all 'pill'
        $ii = 0;
        $isFirstPill = true;
        $recordId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);

        foreach ($pillArray as $formElement) {

            if ($mode != FORM_LOAD && $formElement[FE_DYNAMIC_UPDATE] !== 'yes') {
                continue; // During save/update: Process only FE dynamic_update=yes
            }

            $htmlIdLi = $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_PILL_LI;
            $htmlIdLiA = $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_PILL_LI_A;

            $formElement = $this->evaluate->parseArray($formElement);
            HelperFormElement::explodeParameter($formElement, F_PARAMETER);
            $formElement = HelperFormElement::setLanguage($formElement, $parameterLanguageFieldName);
            if (!empty($formElement[FE_MODE_SQL])) {
                $formElement[FE_MODE] = $formElement[FE_MODE_SQL];
            }

            $ii++;

            if ($formElement[FE_NAME] === '' || $formElement[FE_LABEL] === '') {
                $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
                $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, 'name, label', STORE_SYSTEM);
                throw new \UserFormException("Field 'name' and/or 'label' are empty", ERROR_NAME_LABEL_EMPTY);
            }

            // Anker for pill navigation
//            $a = '<a ' . Support::doAttribute('href', '#' . $this->createAnker($formElement[FE_ID])) . ' data-toggle="tab">' . $formElement[FE_LABEL] . '</a>';

            $attributeLiA = 'data-toggle="tab" ';
            $hrefTarget = '#' . $this->createAnker($formElement[FE_ID], $recordId);

            $htmlFormElementName = HelperFormElement::buildFormElementName($formElement, $recordId);
            switch ($formElement[FE_MODE]) {
                case FE_MODE_SHOW:
                case FE_MODE_REQUIRED:
                    $attributeLi = '';
                    $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLi][API_ELEMENT_ATTRIBUTE][HTML_ATTR_CLASS] = '';
                    $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLiA][API_ELEMENT_ATTRIBUTE][HTML_ATTR_CLASS] = '';
                    break;

                case FE_MODE_READONLY:
                    $hrefTarget = '#';

                    $attributeLi = Support::doAttribute('class', 'disabled');
                    $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLi][API_ELEMENT_ATTRIBUTE][HTML_ATTR_CLASS] = 'disabled';

                    $attributeLiA .= Support::doAttribute('class', 'noclick');
                    $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLiA][API_ELEMENT_ATTRIBUTE][HTML_ATTR_CLASS] = 'noclick';
                    break;

                case FE_MODE_HIDDEN:
//                    $attributeLi = Support::doAttribute('style', 'display: none');
                    $attributeLi = Support::doAttribute('class', 'hidden');
                    $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLi][API_ELEMENT_ATTRIBUTE][HTML_ATTR_CLASS] = 'hidden';
                    break;

                default:
                    throw new \UserFormException("Unknown Mode: " . $formElement[FE_MODE], ERROR_UNKNOWN_MODE);
            }

            $attributeLi .= Support::doAttribute(HTML_ATTR_ID, $htmlIdLi);
            $attributeLi .= Support::doAttribute('title', $formElement[FE_TOOLTIP]);
            $attributeLiA .= Support::doAttribute(HTML_ATTR_ID, $htmlIdLiA);
            $a = Support::wrapTag("<a $attributeLiA" . Support::doAttribute('href', $hrefTarget) . ">", $formElement[FE_LABEL]);
            $json[$htmlFormElementName][API_ELEMENT_UPDATE][$htmlIdLiA][API_ELEMENT_CONTENT] = $formElement[FE_LABEL];

            if ($isFirstPill && $formElement[FE_MODE] != FE_MODE_HIDDEN) {
                $attributeLi .= 'class="active" ';
                $isFirstPill = false;
            }

            if ($ii <= $maxVisiblePill) {
                $pillButton .= '<li role="presentation"' . $attributeLi . ">" . $a . "</li>";
            } else {
                $pillDropdown .= '<li ' . $attributeLi . '>' . $a . "</li>";
            }
        }

        // Pill Dropdown necessary?
        if ($ii > $maxVisiblePill) {
            $htmlDropdown = Support::wrapTag('<ul class="dropdown-menu qfq-form-pill ' . $this->formSpec[F_CLASS_PILL] . '">', $pillDropdown, true);
            $htmlDropdown = '<a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button">more <span class="caret"></span></a>' . $htmlDropdown;
            $htmlDropdown = Support::wrapTag('<li role="presentation" class="dropdown">', $htmlDropdown, false);
        }

        // Check for active remember last pill
        $flagRememberLastPill = ($this->formSpec[F_REMEMBER_LAST_PILL] == '1') ? 'true' : 'false';

        $htmlDropdown = Support::wrapTag('<ul id="' . $this->getTabId() . '" data-active-last-pill="' . $flagRememberLastPill . '" class="nav nav-pills qfq-form-pill ' . $this->formSpec[F_CLASS_PILL] . '" role="tablist">', $pillButton . $htmlDropdown);
        $htmlDropdown = Support::wrapTag('<div class="col-md-12">', $htmlDropdown);

        return $htmlDropdown;
    }

    /**
     * Create an identifier for the pill navigation menu
     *
     * @param $id
     *
     * @return string
     */
    private function createAnker($id, $recordId): string {
        return $this->formSpec[FE_NAME] . '_' . $id . '_' . $recordId;
    }

    /**
     * @return string
     */
    private function getTabId(): string {
        return 'qfqTabs-' . $this->store->getVar(TYPO3_TT_CONTENT_UID, STORE_TYPO3) . '-'
            . $this->formSpec[F_ID] . '-' . $this->store->getVar(CLIENT_RECORD_ID, STORE_TYPO3 . STORE_SIP . STORE_RECORD . STORE_ZERO);
    }

    /**
     * Builds the complete HTML '<form ...>'-tag
     *
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function getFormTag(): string {

        $attribute = $this->getFormTagAttributes();

        $attribute['class'] = 'form-horizontal qfq-form';
        $attribute['data-toggle'] = 'validator';

        $formModeGlobal = Support::getFormModeGlobal($this->formSpec[F_MODE_GLOBAL] ?? '');

        if ($formModeGlobal == F_MODE_REQUIRED_OFF_BUT_MARK || $formModeGlobal == F_MODE_REQUIRED_OFF) {
            $attribute[DATA_REQUIRED_OFF_BUT_MARK] = 'true';
        }

        if (isset($this->formSpec[F_SAVE_BUTTON_ACTIVE]) && $this->formSpec[F_SAVE_BUTTON_ACTIVE] != '0') {
            $attribute[DATA_ENABLE_SAVE_BUTTON] = 'true';
        }

        $honeypot = $this->getHoneypotVars();
        $md5 = $this->buildInputRecordHashMd5();

        $actionUpload = FILE_ACTION . '=' . FILE_ACTION_UPLOAD;
        $actionDelete = FILE_ACTION . '=' . FILE_ACTION_DELETE;

        # Replacing the attributes set via tail / javascript
        $attribute["data-form-id"] = $this->getFormId($this->ttContentUid, $this->formSpec[F_ID], $this->recordId);
        $attribute["data-tabs-id"] = $this->getTabId();
        if (0 < ($recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP))) {
            $attribute["data-delete-url"] = $this->createDeleteUrl($this->formSpec[F_FINAL_DELETE_FORM], $recordId);
        }

        $attribute["data-dirty-url"] = Path::urlApi(API_DIRTY_PHP);
        $attribute["data-submit-to"] = Path::urlApi(API_SAVE_PHP);
        $attribute["data-type-ahead-url"] = Path::urlApi(API_TYPEAHEAD_PHP);
        $attribute["data-refresh-url"] = Path::urlApi(API_LOAD_PHP);
        $attribute["data-file-upload-to"] = Path::urlApi(API_FILE_PHP) . '?' . $actionUpload;
        $attribute["data-file-delete-url"] = Path::urlApi(API_FILE_PHP) . '?' . $actionDelete;
        $attribute["data-log-level"] = 0; # Not sure if raos did implement this fully, but it was hardcoded 0 before.
        $attribute["data-api-delete-url"] = Path::urlApi(API_DELETE_PHP);
        #$attribute["data-sip"] =

        return '<form ' . OnArray::toString($attribute, '=', ' ', "'") . '>' . $honeypot . $md5;
    }


    /**
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function tail(): string {

        $html = '';
        $bottomButton = $this->buildButtons(F_BTN_FOOTER);


        //  <div class="tab-content">
        //       $html .= '<input type="submit" value="Submit">';

//        // Button Save at bottom of form - only if there is a button text given.
//        if ($this->formSpec[F_SUBMIT_BUTTON_TEXT] !== '') {
//            // Default setzen:
//            $this->fillWrapLabelInputNote($this->formSpec[F_BS_LABEL_COLUMNS], $this->formSpec[F_BS_INPUT_COLUMNS], $this->formSpec[F_BS_NOTE_COLUMNS]);
//
//            $buttonText = $this->formSpec[F_SUBMIT_BUTTON_TEXT];
//
//            $htmlElement = $this->buildButtonCode('save-button', $buttonText, $this->formSpec[F_SUBMIT_BUTTON_TOOLTIP],
//                $this->formSpec[F_SUBMIT_BUTTON_GLYPH_ICON], '', $this->formSpec[F_BUTTON_ON_CHANGE_CLASS],
//                $this->formSpec[F_SUBMIT_BUTTON_CLASS]);
//
//            //New save button style if save button text is given
//            $newButtonStyle = '<div class="row"><div class="col-md-12" style="height: 45px; padding-top: 8px;">';
//            $lastDiv = '</div></div>';
//
//            //finish elements and close tag-form befour wrapping save button
//            $html = $this->wrapItem(WRAP_SETUP_ELEMENT, $html);
//
//            $html .= $newButtonStyle;
//            $html .= $this->wrapItem(WRAP_SETUP_LABEL, '');
//            $html .= $this->wrapItem(WRAP_SETUP_INPUT, $htmlElement);
//            $html .= $this->wrapItem(WRAP_SETUP_NOTE, '');
//            $html .= $lastDiv;
//        }

        if ($this->formSpec[F_SUBMIT_BUTTON_TEXT] !== '') {
            $html = $this->buildSaveButton(F_BTN_FOOTER);
        }
        $html .= $bottomButton;
        $html .= '</div> <!--class="tab-content" -->';

        $html .= '</form>';

        # Add $this->clientJsAll
        if ($this->clientJsAll != '') {
            $html .= '<script>' . $this->clientJsAll . '</script>';
        }

        $html .= '</div>';  //  <div class="container-fluid"> === main <div class=...> around everything


        return $html;
    }

    /**
     * @param array $formElement
     * @param       $htmlFormElementName
     * @param       $value
     *
     * @param array $json
     * @return mixed
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function buildPill(array $formElement, $htmlFormElementName, $value, array &$json): mixed {
        $html = '';

        if ($formElement[FE_MODE] == FE_MODE_HIDDEN) {
            return '';
        }

        // save parent processed FE's
        $tmpStore = $this->feSpecNative;

        // child FE's
        $this->feSpecNative = $this->dbArray[$this->dbIndexQfq]->getNativeFormElements(SQL_FORM_ELEMENT_SPECIFIC_CONTAINER,
            ['yes', $this->formSpec["id"], 'native,container', $formElement['id']], $this->formSpec);

        $html = $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE_SUBRECORD, 0, $json);

        // restore parent processed FE's
        $this->feSpecNative = $tmpStore;

        return $html;
    }

    /**
     * Wrap content with $wrapArray or, if specified use $formElement[$wrapName]. Inject $htmlId in wrap.
     *
     * Result:
     * - if $bsColumns==0 and empty $formElement[$wrapName]: no wrap
     * - if $formElement[$wrapName] is given: wrap with that one. Else: wrap with $wrapArray
     * - if $htmlId is give, inject it in $wrap.
     *
     * @param array $formElement Complete FormElement, especially some FE_WRAP
     * @param string $htmlElement Content to wrap.
     * @param string $wrapName FE_WRAP_ROW, FE_WRAP_LABEL, FE_WRAP_INPUT, FE_WRAP_NOTE
     * @param int $bsColumns
     * @param array $wrapArray System wide Defaults: [ 'open wrap', 'close wrap' ]
     * @param string $htmlId
     * @param string $class
     *
     * @return string Wrapped $htmlElement
     * @throws \CodeException
     * @throws \UserFormException
     */
    private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray, $htmlId = '', $class = ''): string {

        // If $bsColumns==0: do not wrap with default.
        if ($bsColumns == '0') {
            $wrapArray[0] = '';
            $wrapArray[1] = '';
        }

        // If there is a 'per FormElement'-wrap, take it.
        if (isset($formElement[$wrapName])) {
            $wrapArray = explode('|', $formElement[$wrapName], 2);
        }

        if (count($wrapArray) != 2) {
            throw new \UserFormException("Need open & close wrap token for FormElement.parameter" . $wrapName . " - E.g.: <div ...>|</div>", ERROR_MISSING_VALUE);
        }

        if ($wrapArray[0] != '') {
            $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'id', $htmlId);
            $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'class', $class); // might be problematic, if there is already a 'class' defined.
            if ($wrapName == FE_WRAP_LABEL) {
                $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'style', 'text-align: ' . $formElement[F_FE_LABEL_ALIGN] . ';'); // might be problematic, if there is already a 'class' defined.
            }
            // Insert 'required="required"' for checkboxes and radios
            if ($wrapName == FE_WRAP_INPUT && $formElement[FE_MODE] == FE_MODE_REQUIRED &&
                ($formElement[FE_TYPE] == FE_TYPE_CHECKBOX || $formElement[FE_TYPE] == FE_TYPE_RADIO)) {
                $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'required', 'required'); // might be problematic, if there is already a 'class' defined.
            }
        }

        return $wrapArray[0] . $htmlElement . $wrapArray[1];
    }

    /**
     * @param array $formElement Complete FormElement, especially some FE_WRAP
     * @param string $htmlElement Content to wrap.
     * @param        $htmlFormElementName
     *
     * @return string               Wrapped $htmlElement
     * @throws \CodeException
     * @throws \UserFormException
     */
    public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementName): string {
        $html = '';
        $htmlLabel = '';
        $classHideRow = '';
        $classHideElement = '';
        $addClassRequired = array();

        if ($formElement[FE_MODE] == FE_MODE_HIDDEN) {
            if ($formElement[FE_FLAG_ROW_OPEN_TAG] && $formElement[FE_FLAG_ROW_CLOSE_TAG]) {
                $classHideRow = 'hidden';
            } else {
                $classHideElement = 'hidden';
            }
        }

        if (($formElement[FE_MODE] == FE_MODE_REQUIRED || $formElement[FE_MODE] == FE_MODE_SHOW_REQUIRED) &&
            $formElement[FE_INDICATE_REQUIRED] == '1') {
            $addClassRequired = HelperFormElement::getRequiredPositionClass($formElement[F_FE_REQUIRED_POSITION]);
        }

        // htmlBefore - inside form group (dynamic update compatibility) - use div to show it as a own block element
        if (!empty($formElement[FE_HTML_BEFORE])) {
            $html .= Support::wrapTag('<div class="col-md-12">', $formElement[FE_HTML_BEFORE]);
        }

        // Label
        if ($formElement[FE_BS_LABEL_COLUMNS] != '0') {
            $htmlLabel = HelperFormElement::buildLabel($htmlFormElementName, $formElement[FE_LABEL], $addClassRequired[FE_LABEL] ?? '');
        }

        $html .= $this->customWrap($formElement, $htmlLabel, FE_WRAP_LABEL, $formElement[FE_BS_LABEL_COLUMNS],
            [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL);

        // Input
        if (!empty($addClassRequired[FE_INPUT])) {
            $htmlElement = Support::wrapTag('<span class="' . $addClassRequired[FE_INPUT] . '">', $htmlElement);
        }
        $html .= $this->customWrap($formElement, $htmlElement, FE_WRAP_INPUT, $formElement[FE_BS_INPUT_COLUMNS],
            [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]],
            $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT, $classHideElement);


        if (!empty($addClassRequired[FE_NOTE])) {
            $formElement[FE_NOTE] = Support::wrapTag('<span class="' . $addClassRequired[FE_NOTE] . '">', $formElement[FE_NOTE]);
        }
        // Note
        $html .= $this->customWrap($formElement, $formElement[FE_NOTE], FE_WRAP_NOTE, $formElement[FE_BS_NOTE_COLUMNS],
            [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE);

        // htmlAfter - inside form group (dynamic update compatibility) - use div to show it as a own block element
        if (!empty($formElement[FE_HTML_AFTER])) {
            $html .= Support::wrapTag('<div class="col-md-12">', $formElement[FE_HTML_AFTER]);
        }

        // Row
        $openTag = $formElement[FE_FLAG_ROW_OPEN_TAG] ? $this->getRowOpenTag($classHideRow) : '';
        $closeTag = $formElement[FE_FLAG_ROW_CLOSE_TAG] ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] : '';

        $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, -1, [$openTag, $closeTag], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_ROW);

        return $html;
    }

    /**
     * @param $formElement
     * @param $elementHtml
     *
     * @return string
     */
    public function buildRowPill(array $formElement, $elementHtml): string {
        $html = '';

        $html .= Support::wrapTag('<div class="col-md-12 qfq-form-body ' . $this->formSpec[F_CLASS_BODY] . ' "style="border-bottom:none;">', $elementHtml);

        if ($formElement[FE_MODE] == FE_MODE_HIDDEN) {
            $class = ' hidden';
        } else {
            $class = $this->isFirstPill ? 'active ' : '';
            $this->isFirstPill = false;
        }
        $recordId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);

        $html = Support::wrapTag('<div role="tabpanel" class="tab-pane ' . $class . '" id="' . $this->createAnker($formElement['id'], $recordId) . '">', $html);


        return $html;
    }

    /**
     * Builds a fieldset
     *
     * @param array $formElement
     * @param $elementHtml
     * @return mixed
     */
    public function buildRowFieldset(array $formElement, $elementHtml): mixed {
        $html = $elementHtml;

        return $html;
    }

    /**
     * Builds a templateGroup
     *
     * @param $formElement
     * @param $elementHtml
     * @return mixed
     */
    public function buildRowTemplateGroup(array $formElement, $elementHtml): mixed {
        $html = $elementHtml;

        return $html;
    }

    /**
     * @param array $formElement
     * @param $elementHtml
     *
     * @return string
     * @throws \CodeException
     */
    public function buildRowSubrecord(array $formElement, $elementHtml): string {


        $attribute = ($formElement[FE_MODE] == FE_MODE_HIDDEN) ? ' style="display: none;"' : '';
        $attribute .= Support::doAttribute('id', $formElement[FE_HTML_ID]);
        #return $elementHtml;
        return Support::wrapTag("<div class='form-group clearfix' $attribute>", $elementHtml);
    }

    /**
     * Builds complete 'form'.
     *
     * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
     *
     * @param bool $htmlElementNameIdZero
     * @param array $latestFeSpecNative
     * @return string|array   $mode=LOAD_FORM: The whole form as HTML, $mode=FORM_UPDATE /FORM_SAVE: array of all
     *                        formElement.dynamicUpdate-yes  values/states
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function process($mode, $htmlElementNameIdZero = false, $latestFeSpecNative = array()): array|string {

        $json = array();
        $data = parent::process($mode, $htmlElementNameIdZero, $latestFeSpecNative);

        switch ($mode) {
            case FORM_SAVE:
            case FORM_UPDATE:
                $pillArray = OnArray::filter($this->feSpecNative, FE_TYPE, FE_TYPE_PILL);
                $this->buildPillNavigation($mode, $pillArray, $json);
                $data = array_merge($data, $json);
                break;
            default:
                break;
        }

        return $data;
    }

    /**
     * Builds two (optionally three) buttons as provided by the developer.
     * Can be used to jump to previous/next record within the form.
     *
     * @param $btnPreviousNextSqlArr
     * @param $currentRecordId
     * @param $btnPreviousNextLoopFlag
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function buildPreviousNextButtons($btnPreviousNextSqlArr, $currentRecordId, $btnPreviousNextLoopFlag): string {

        // No records selected
        if (empty($btnPreviousNextSqlArr)) {
            return '';
        }

        $recordIdArr = array_column($btnPreviousNextSqlArr, F_BTN_PREVIOUS_NEXT_SQL_ID);
        $currentRecordIndex = array_search($currentRecordId, $recordIdArr);

        $btnPreviousGiven = isset($btnPreviousNextSqlArr[$currentRecordIndex][F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS]);
        $btnNextGiven = isset($btnPreviousNextSqlArr[$currentRecordIndex][F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT]);
        $firstRecordFlag = ($currentRecordIndex === array_key_first($recordIdArr));
        $lastRecordFlag = ($currentRecordIndex === array_key_last($recordIdArr));

        // r=0 disables both buttons
        if (!$currentRecordId) {
            $firstRecordFlag = true;
            $lastRecordFlag = true;
            $btnPreviousNextLoopFlag = false;
        }

        $btnCurrent = (!$currentRecordId) ? null : $btnPreviousNextSqlArr[$currentRecordIndex][F_BTN_PREVIOUS_NEXT_SQL_BTN_CURRENT] ?? '';

        // Keyword btnPrevious is used
        if ($btnPreviousGiven) {
            $btnPrevious = $btnPreviousNextSqlArr[$currentRecordIndex - 1][F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS] ?? null;
            $btnPrevious = $btnPrevious ?? $btnPreviousNextSqlArr[array_key_last($btnPreviousNextSqlArr)][F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS];

            // btnPreviousNextLoop is not enabled and first record is currently selected
            // Button should be disabled
            if (!$btnPreviousNextLoopFlag && $firstRecordFlag) {
                $btnPreviousArr = Token::explodeTokenString($btnPreviousNextSqlArr[array_key_last($btnPreviousNextSqlArr)][F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS]);
                $btnPreviousArr['r'] = '3';
                $btnPreviousArr['t'] = '';
                $btnPreviousArr['o'] = F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS_TOOLTIP;
                $btnPrevious = KeyValueStringParser::unparse($btnPreviousArr, PARAM_KEY_VALUE_DELIMITER, PARAM_DELIMITER);
            }

            // Keyword btnPrevious is not used
            // Button is built from scratch
        } else {
            $pageSlug = $this->store->getVar(TYPO3_PAGE_SLUG, STORE_TYPO3);
            $form = $this->formSpec[F_NAME];
            $id = $recordIdArr[$currentRecordIndex - 1] ?? $recordIdArr[array_key_last($recordIdArr)];

            // btnPreviousNextLoop is not enabled and first record is currently selected
            // Button should be disabled
            $renderModeTooltip = !$btnPreviousNextLoopFlag && $firstRecordFlag ? '|r:3|o:' . F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS_TOOLTIP : '';

            $btnPrevious = 'p:' . $pageSlug . '?form=' . $form . '&r=' . $id . '|s|b|G:' . F_BTN_PREVIOUS_NEXT_SQL_BTN_PREVIOUS_GLYPHICON . $renderModeTooltip;
        }

        // Keyword btnNext is used
        if ($btnNextGiven) {
            $btnNext = $btnPreviousNextSqlArr[$currentRecordIndex + 1][F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT] ?? null;
            $btnNext = $btnNext ?? $btnPreviousNextSqlArr[array_key_first($btnPreviousNextSqlArr)][F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT];

            // btnPreviousNextLoop is not enabled and last record is currently selected
            // Button should be disabled
            if (!$btnPreviousNextLoopFlag && $lastRecordFlag) {
                $btnPreviousArr = Token::explodeTokenString($btnPreviousNextSqlArr[array_key_first($recordIdArr)][F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT]);
                $btnPreviousArr['r'] = '3';
                $btnPreviousArr['t'] = '';
                $btnPreviousArr['o'] = F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT_TOOLTIP;
                $btnNext = KeyValueStringParser::unparse($btnPreviousArr, PARAM_KEY_VALUE_DELIMITER, PARAM_DELIMITER);
            }

            // Keyword btnNext is not used
            // Button is built from scratch
        } else {
            $pageSlug = $this->store->getVar(TYPO3_PAGE_SLUG, STORE_TYPO3);
            $form = $this->formSpec[F_NAME];
            $id = $recordIdArr[$currentRecordIndex + 1] ?? $recordIdArr[array_key_first($recordIdArr)];

            // btnPreviousNextLoop is not enabled and last record is currently selected
            // Button should be disabled
            $renderModeTooltip = !$btnPreviousNextLoopFlag && $lastRecordFlag ? '|r:3|o:' . F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT_TOOLTIP : '';

            $btnNext = 'p:' . $pageSlug . '?form=' . $form . '&r=' . $id . '|s|b|G:' . F_BTN_PREVIOUS_NEXT_SQL_BTN_NEXT_GLYPHICON . $renderModeTooltip;
        }

        $previousLink = $this->link->renderLink($btnPrevious);
        $nextLink = $this->link->renderLink($btnNext);
        $currentLink = $this->link->renderLink($btnCurrent);

        return $previousLink . $currentLink . $nextLink;
    }
}
