<?php

namespace IMATHUZH\Qfq\Core\Renderer;

use IMATHUZH\Qfq\Core\Database\DatabaseManager;
use IMATHUZH\Qfq\Core\Form\Form;
use IMATHUZH\Qfq\Core\Form\FormElement\AbstractFormElement;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Renderer\Element\ButtonRenderer;
use IMATHUZH\Qfq\Core\Renderer\Element\ElementRendererInterface;
use IMATHUZH\Qfq\Core\Renderer\Element\SpinnerRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\AnnotateRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\ChatRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\CheckboxRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\DatetimeRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\EditorRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\FieldsetRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\ImageCutRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\InputRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\InputCollectionJsonRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\NoteRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\PillRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\RadioButtonRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\SelectRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\SubrecordRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\TemplateGroupRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\Base\UploadRenderer;
use IMATHUZH\Qfq\Core\Renderer\FormElement\FormElementRenderer;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;


class BaseRenderer {

    // Instances / Managers
    protected ?Store $store = null;
    protected ?DatabaseManager $databaseManager = null;

    protected ?FormElementRenderer $colorPickerRenderer = null;
    // ElementRenderers
    protected ?ElementRendererInterface $buttonRenderer = null;
    protected ?ElementRendererInterface $spinnerRenderer = null;

    // Container FormElementRenderers
    protected ?FormElementRenderer $pillRenderer = null;
    protected ?FormElementRenderer $fieldsetRenderer = null;
    protected ?FormElementRenderer $templateGroupRenderer = null;
    // Native FormElementRenderers
    protected ?FormElementRenderer $noteRenderer = null;
    protected ?FormElementRenderer $inputRenderer = null;
    protected ?FormElementRenderer $checkboxRenderer = null;
    protected ?FormElementRenderer $radioRenderer = null;
    protected ?FormElementRenderer $selectRenderer = null;
    protected ?FormElementRenderer $subrecordRenderer = null;
    protected ?FormElementRenderer $editorRenderer = null;
    protected ?FormElementRenderer $datetimeRenderer = null;
    protected ?FormElementRenderer $uploadRenderer = null;
    protected ?FormElementRenderer $imageCutRenderer = null;
    protected ?FormElementRenderer $annotateRenderer = null;
    protected ?FormElementRenderer $chatRenderer = null;
    protected ?FormElementRenderer $inputCollectionJsonRenderer = null;

    public array $wrap;
    //<editor-fold desc="Singleton">
    protected static ?BaseRenderer $instance = null;
    protected string $clientJsAll = '';

    /**
     * @return BaseRenderer
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public static function getInstance(): BaseRenderer {
        if (self::$instance == null) {
            $specifiedRenderer = "IMATHUZH\Qfq\Core\Renderer\\" . Store::getVar(SYSTEM_RENDERER, STORE_SYSTEM);
            if (class_exists($specifiedRenderer)) {
                self::$instance = new $specifiedRenderer;
            } else {
                self::$instance = new BaseRenderer();
            }
        }
        return self::$instance;
    }

    //</editor-fold>

    /**
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct() {
        $this->store = Store::getInstance();
        $this->databaseManager = DatabaseManager::getInstance();

        $this->pillRenderer = new PillRenderer();
        $this->fieldsetRenderer = new FieldsetRenderer();
        $this->templateGroupRenderer = new TemplateGroupRenderer();

        $this->buttonRenderer = new ButtonRenderer();
        $this->spinnerRenderer = new SpinnerRenderer();

        $this->inputRenderer = new InputRenderer();
        $this->checkboxRenderer = new CheckboxRenderer();
        $this->radioRenderer = new RadioButtonRenderer();
        $this->subrecordRenderer = new SubrecordRenderer();
        $this->selectRenderer = new SelectRenderer();
        $this->noteRenderer = new NoteRenderer();
        $this->editorRenderer = new EditorRenderer();
        $this->datetimeRenderer = new DatetimeRenderer();
        $this->imageCutRenderer = new ImageCutRenderer();
        $this->annotateRenderer = new AnnotateRenderer();
        $this->uploadRenderer = new UploadRenderer();
        $this->chatRenderer = new ChatRenderer();
        $this->inputCollectionJsonRenderer = new InputCollectionJsonRenderer();
    }

    /**
     * @param Form $form
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     */
    public function renderFormHtml(Form $form): string {
        $output = $this->renderFormHead($form);

        $formElementsHtml = ""; // FEs need to be rendered after additional FEs

        // Non-Multi
        if (!isset($form->specFinal[F_MULTI_SQL]) || $form->specFinal[F_MULTI_SQL] == '') {
            for ($i = 0; $i < count($form->formElements); $i++) {
                $formElementsHtml .= $this->renderFormElementHtml($form->formElements[$i]);
            }
        } // Multi
        else {
            $formElementsHtml .= $this->renderMultiFormHtml($form);
        }

        $output .= $this->renderAdditionalFormElements();
        $output .= $formElementsHtml;
        $output .= $this->renderFormTail($form, RENDER_MODE_HTML);

        return $output;
    }

    /**
     * Renders the 'body' of a multiform. (Excl. form head and form tail)
     *
     * @param Form $form
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \IMATHUZH\Qfq\Core\Exception\RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderMultiFormHtml(Form $form): string {

        $addRow = isset($form->specFinal[F_MULTIFORM_ADD_ROW]) && $form->specFinal[F_MULTIFORM_ADD_ROW] == '1';
        $deleteRow = isset($form->specFinal[F_MULTIFORM_DELETE_ROW]) && $form->specFinal[F_MULTIFORM_DELETE_ROW] == '1';
        $flagTable = true;

        // No rows: Display Message
        if (count($form->formElements) == 0 || count(array_values($form->formElements)[0]) == 0) {
            return $form->specFinal[F_MULTI_MSG_NO_RECORD];
        }

        // Prepare Wrap
        if (!isset($form->specFinal[F_MULTI_WRAP])) {
            $form->specFinal[F_MULTI_WRAP] = '';
        }

        if (!isset($form->specFinal[F_MULTI_CELL_WRAP])) {
            $form->specFinal[F_MULTI_CELL_WRAP] = '';
        }

        if ($form->specFinal[F_MULTI_WRAP] == '') {
            $tableClass = $form->specFinal[F_MULTI_TABLE_CLASS] ?? 'table table-multi-form qfq-table-100';
            $form->specFinal[F_MULTI_WRAP] = "<table class='$tableClass'>";
            $form->specFinal[F_MULTI_CELL_WRAP] = '<td>';
        } else {
            $flagTable = false;
            if ($form->specFinal[F_MULTI_CELL_WRAP] == '') {
                $form->specFinal[F_MULTI_CELL_WRAP] = '<div>';
            }
        }

        // Prepare a dummy element
        if ($addRow) $this->addMultiformDummyRow($form);

        // PROCESS_ROW - activate if it is set as a form parameter or if it is included in multiSql
        $renderProcessRow = isset($form->specFinal[F_PROCESS_ROW])
            || isset($form->specFinal[F_MULTI_SQL_RESULT][0][F_PROCESS_ROW])
            || isset($form->specFinal[F_MULTI_SQL_RESULT][0][F_PROCESS_ROW_COLUMN]);
        $processRowTitle = $form->specFinal[F_PROCESS_ROW] ?? F_PROCESS_ROW;

        // Prepare Table Head (if no custom wrap)
        $headerRow = '';

        $thWrap = $flagTable ? '<th>' : '<div style="font-weight: bold">';

        // Header Cell processRow (always disable sorting and filtering)
        if ($renderProcessRow) {
            $processRowThWrap = $flagTable ? "<th class='sorter-false filter-false'>" : '<div style="font-weight: bold">';
            $headerRow .= Support::wrapTag($processRowThWrap,
                '<label class="checkbox process-row-all process-row-label-header"><input type="checkbox"><span>'
                . $processRowTitle
                . '</span></label>');
        }

        // Header Cell per visible result of multi sql
        if (isset($form->specFinal[F_MULTI_SQL_RESULT])) {
            foreach ($form->specFinal[F_MULTI_SQL_RESULT][0] as $key => $value) {
                if (($key[0] ?? '') != '_') {
                    $arr = KeyValueStringParser::parse($key, '=', '|', KVP_IF_VALUE_EMPTY_COPY_KEY);
                    $columnTitle = '';
                    $currentThWrap = $thWrap;

                    foreach ($arr as $attribute => $attrValue) {
                        switch ($attribute) {
                            case SUBRECORD_COLUMN_TH_CLASS:
                                $currentThWrap = "<th class='$attrValue'>";
                                break;
                            default:
                                $columnTitle = $attrValue;
                                break;
                        }
                    }
                    $headerRow .= Support::wrapTag($currentThWrap, $columnTitle);
                }
            }
        }


        // Header Cell per FE (for 'Edit FE' link)
        foreach (array_values($form->formElements)[0] as $fe) {
            $editFeHtml = '';
            // Toggleable Edit FE Link
            if ($form->showDebugInfoFlag) {
                $feEditUrl = $fe->formEditorUrl;
                $titleAttr = $fe->formEditorTooltip;
                $icon = Support::wrapTag('<span class="' . GLYPH_ICON . ' ' . GLYPH_ICON_EDIT . '">', '');
                $editFeHtml = ' ' . Support::wrapTag("<a class='hidden " . CLASS_FORM_ELEMENT_EDIT . "' href='$feEditUrl' $titleAttr>", $icon);
            }

            // Determine thWrap for this FE (with optional thClass from FormElement.parameter)
            $currentThWrap = $thWrap;
            if (!empty($fe->attributes[SUBRECORD_COLUMN_TH_CLASS])) {
                $currentThWrap = "<th class='" . $fe->attributes[SUBRECORD_COLUMN_TH_CLASS] . "'>";
            }

            // Required marker
            if ($fe->attributes[FE_MODE] === FE_MODE_REQUIRED) {
                $required = Support::insertAttribute($currentThWrap, HTML_ATTR_CLASS, $fe->addClassRequired[FE_LABEL]);
                $headerRow .= Support::wrapTag($required, $fe->attributes[FE_LABEL] . $editFeHtml);
                continue;
            }

            $headerRow .= Support::wrapTag($currentThWrap, $fe->attributes[FE_LABEL] . $editFeHtml);
        }

        // Delete Row
        if ($deleteRow) {
            $headerRow .= Support::wrapTag($thWrap, '');
        }

        if ($flagTable) $headerRow = Support::wrapTag('<thead><tr>', $headerRow);

        // Render the Content.
        $content = "";
        $multiSqlIndex = 0;
        $idName = $form->specFinal[F_MULTI_SQL_ID_NAME];
        $counter = 1; // counter to check if id is 0 then add a new index. example-0-1, example-0-2

        foreach ($form->formElements as $rowId => $feArray) {
            $rowHtml = "";
            // Render processRow (if active)
            if ($renderProcessRow) {
                $processRowChecked = "";
                if (isset($form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][F_PROCESS_ROW_COLUMN])) {
                    $processRowChecked = $form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][F_PROCESS_ROW_COLUMN] ? "checked" : "";
                } else if (isset($form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][F_PROCESS_ROW])) {
                    $processRowChecked = $form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][F_PROCESS_ROW] ? "checked" : "";
                }
                $processCounter = ($form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][$idName] == 0) ? $counter : null;
                $processRowName = HelperFormElement::buildFormElementName([FE_NAME => F_PROCESS_ROW_COLUMN], $form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][$idName], $processCounter);
                if ($form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex][$idName] == 0) $counter++;

                $rowHtml .= Support::wrapTag($thWrap, '<label class="checkbox"><input name="' . $processRowName . '" type="checkbox" ' . $processRowChecked . '></label>');
            }


            // Render Rows produced by MultiFormSql
            foreach ($form->specFinal[F_MULTI_SQL_RESULT][$multiSqlIndex] as $key => $value) {
                if (($key[0] ?? '') != '_' && $key != F_PROCESS_ROW) {
                    $rowHtml .= Support::wrapTag($form->specFinal[F_MULTI_CELL_WRAP], $value);
                }
            }
            $multiSqlIndex++;

            // Render Form Element Rows
            for ($i = 0; $i < count($feArray); $i++) {
                $recordId = explode('-', $feArray[$i]->htmlAttributes[HTML_ATTR_NAME])[1];
                if ($recordId == 0 && count(explode('-', $feArray[$i]->htmlAttributes[HTML_ATTR_NAME])) == 2) {
                    $feArray[$i]->htmlAttributes[HTML_ATTR_NAME] .= '-0'; // Add secondary index to end of HTML name
                }

                $formElementHtml = $this->renderFormElementHtml($feArray[$i], RENDER_MODE_HTML, true);
                $rowHtml .= Support::wrapTag($form->specFinal[F_MULTI_CELL_WRAP], $formElementHtml);
            }

            // Render DeleteRow Button
            if ($deleteRow) {
                $deleteBtnHtml = '';

                // Records that don't exist: Render a delete button that will remove the row from DOM
                if (str_starts_with($rowId, '0_') || $rowId == 'dummy-row') {
                    $deleteBtnSpec = array();
                    $deleteBtnSpec[] = '';
                    //$deleteBtnSpec['buttonHtmlId'] = MULTIFORM_DELETE_ROW_BTN;
                    $deleteBtnSpec['tooltip'] = 'Delete row';
                    $deleteBtnSpec['class'] = 'deleteRow';
                    $deleteBtnSpec['icon'] = GLYPH_ICON_DELETE;
                    $deleteBtnSpec['class'] = 'btn btn-default ' . MULTIFORM_DELETE_ROW_BTN;
                    $deleteBtnHtml = $this->buttonRenderer->render($deleteBtnSpec);
                } // Existing records: Render an actual delete link
                else {
                    $deleteString = TOKEN_ACTION_DELETE . ':' . TOKEN_ACTION_DELETE_AJAX
                        . '|' . TOKEN_GLYPH . ':' . GLYPH_ICON_DELETE
                        . '|' . TOKEN_SIP . '|' . TOKEN_BOOTSTRAP_BUTTON
                        . '|' . TOKEN_URL_PARAM . ':form=' . $form->specFinal[F_NAME] . '&r=' . explode('_', $rowId)[0] /*$row[$idName]*/ . '&' . FLAG_MULTIFORM . '=true'
                        . '|' . TOKEN_TOOL_TIP . ':Delete';

                    $link = new Link(Store::getSipInstance());
                    $deleteBtnHtml = $link->renderLink($deleteString);
                }

                $rowHtml .= Support::wrapTag($form->specFinal[F_MULTI_CELL_WRAP], $deleteBtnHtml);
            }

            $dummyRowClass = $multiSqlIndex == count($form->specFinal[F_MULTI_SQL_RESULT]) && $addRow ? ' class="dummy-row"' : ' class="record"';
            if ($flagTable) $rowHtml = Support::wrapTag('<tr' . $dummyRowClass . '>', $rowHtml);
            $content .= $rowHtml;
        }
        if ($flagTable) $content = '<tbody>' . $content . '</tbody>';


        // Add Row Button
        $addRowButtonHtml = '';
        if ($addRow) {
            $buttonSpec = array();
            $buttonSpec[BTN_HTML_ID] = MULTIFORM_ADD_ROW_BTN . '-' . $form->getHtmlId();
            $buttonSpec[BTN_TOOLTIP] = 'Add new row';
            $buttonSpec[BTN_ICON] = GLYPH_ICON_NEW;
            $buttonSpec[BTN_CLASS] = $form->specFinal[F_SAVE_BUTTON_CLASS] . ' addRowButton';
            $addRowButtonHtml = $this->buttonRenderer->render($buttonSpec);
        }

        return Support::wrapTag($form->specFinal[F_MULTI_WRAP], $headerRow . $content) . $addRowButtonHtml;
    }

    /**
     * Multiform: Adds a dummy row to form->formElements, which will be rendered as a hidden row and used to make the 'add row' functionality work
     * Note that form->formElements is an array that contains all rows. Each row is another array!
     *
     * @param Form $form
     * @return void
     */
    private function addMultiformDummyRow(Form $form): void {
        // Use the first record as a dummyRow to ensure new elements have the same structure
        $dummyRow = $form->formElements[array_keys($form->formElements)[0]];

        for ($i = 0; $i < count($dummyRow); $i++) {
            // Clone FE so that the original is not altered
            $feClone = clone $dummyRow[$i];
            // Unset all values of FEs within dummy row (so that new rows are added blank)
            unset($feClone->htmlAttributes['value']);
            // Set Dummy Row id to 0
            $feClone->htmlAttributes[HTML_ATTR_NAME] = explode('-', $feClone->htmlAttributes[HTML_ATTR_NAME])[0] . '-0-0';
            $dummyRow[$i] = $feClone;
        }
        $form->formElements[FE_DUMMY_ROW] = $dummyRow;

        $dummyMultiSqlResult = $form->specFinal[F_MULTI_SQL_RESULT][0];
        foreach ($dummyMultiSqlResult as $key => $value) {
            if ($key == 'id' || $key == '_id' || $key == F_PROCESS_ROW_COLUMN) {
                $dummyMultiSqlResult[$key] = 0;
            } else {
                $dummyMultiSqlResult[$key] = '';
            }
        }
        array_push($form->specFinal[F_MULTI_SQL_RESULT], $dummyMultiSqlResult);
    }


    /**
     * Render the elements that have DYNAMIC UPDATE as a json for the client to update them.
     * Includes update to form title too.
     *
     * @param Form $form
     * @return array
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function renderFormJson(Form $form): array {
        $json = array();

        // Non-Multi-Form
        if (!isset($form->specFinal[F_MULTI_SQL]) || $form->specFinal[F_MULTI_SQL] === '') {
            // Render Non-Pills
            for ($i = 0; $i < count($form->formElements); $i++) {
                if ($form->formElements[$i]->attributes[FE_TYPE] != FE_TYPE_PILL) $json[] = $this->renderFormElementJson($form->formElements[$i]);
            }
            // Render Pills
            $pillJson = array();
            for ($i = 0; $i < count($form->formElements); $i++) {
                if ($form->formElements[$i]->attributes[FE_TYPE] == FE_TYPE_PILL) $pillJson[] = $this->renderFormElementJson($form->formElements[$i]);
            }
            // Merge Arrays
            $json = array_merge($json, $pillJson);
        } // Multi Form
        else {
            foreach ($form->formElements as $recordKey => $feArray) {
                for ($i = 0; $i < count($feArray); $i++) {
                    // TODO: Dynamic Update for MultiForm (has never worked in the past but would be super sexy)
                    //$json[] = $this->renderFormElementJson($feArray[$i]);
                }
            }
        }


        $formArray[F_TITLE] = $form->specFinal[F_UNEVALUATED_TITLE];
        $evaluatedTitle = $form->evaluate->parseArray($formArray);

        // If form title has changed, add new title to JSON
        if ($form->specFinal[F_TITLE] != $evaluatedTitle[F_TITLE]) {
            $element = array(
                API_FORM_UPDATE_FORM_ELEMENT => CLASS_FORM_TITLE,
                FE_VALUE => $evaluatedTitle[F_TITLE]
            );
            $json[] = $element;
        }

        return $json;
    }

    /**
     * @param Form $form
     * @param string $renderMode
     * @return string
     */
    protected function renderFormHead(Form $form, string $renderMode = RENDER_MODE_HTML): string {
        $output = "";
        return $output;
    }

    /**
     * @param Form $form
     * @param string $renderMode
     * @return string
     */
    protected function renderFormTail(Form $form, string $renderMode = RENDER_MODE_HTML): string {
        $output = "";
        return $output;
    }

    /**
     * Render a formElement in HTML
     * @param AbstractFormElement $formElement
     * @param bool $inputOnly No label, note or wrap will be rendered if this is set to true. E.g. Inline Edit.
     * @return string
     * @throws \UserFormException
     */
    public function renderFormElementHtml(AbstractFormElement $formElement, bool $inputOnly = false): string {
        $feRenderer = $this->getFormElementRenderer($formElement);
        
        if ($feRenderer == null) return "";
        $this->clientJsAll .= $feRenderer->getClientJS($formElement);

        if ($inputOnly && $formElement->attributes[FE_CLASS] == FE_CLASS_NATIVE) {
            $inputElementHTML = $feRenderer->renderInput($formElement);
            if (isset($formElement->attributes[FE_INPUT_TOGGLE])) {
                $inputElementHTML = HelperFormElement::buildToggleInput($inputElementHTML, $formElement);
            }
            return $inputElementHTML . $feRenderer->renderToolTip($formElement);
//            return $feRenderer->renderInput($formElement) . $feRenderer->renderToolTip($formElement);
        } else {
            return $feRenderer->renderHtml($formElement);
        }
    }

    /**
     * Render a formElement as an array that can later be converted to JSON
     *
     * @param AbstractFormElement $formElement
     * @return array
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function renderFormElementJson(AbstractFormElement $formElement): array {
        $feRenderer = $this->getFormElementRenderer($formElement);
        if ($feRenderer == null) return [];
        return $feRenderer->renderJson($formElement);
    }

    /**
     * Add input filter-specific data attributes to rendered FormElement HTML.
     * Used by Report's _input column to enable AJAX filtering functionality.
     *
     * @param string $html Rendered input HTML
     * @param string $sip SIP token for AJAX calls
     * @param string $target Target container CSS selector
     * @param string $paramName Parameter name for filter
     * @param string $countSip SIP token for count refresh (optional)
     * @param string $label Label text for filter display (optional)
     * @return string Modified HTML with filter attributes
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    /**
     * Note: Main SIP is NOT added as data-sip attribute anymore.
     * JS reads SIP from window.qfqFilterSips[target] which is generated
     * at end of page render with all allowedValues included.
     */
    public function feAddFilterAttributes(string $html, string $sip, string $target, string $paramName, string $countSip = '', string $label = ''): string {
        // Get base URL from store
        $baseUrl = Store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM);

        // Build data attributes string (data-sip no longer included)
        $dataAttrs = ' ' . DATA_TARGET . '="' . htmlspecialchars($target) . '"'
            . ' ' . DATA_PARAM . '="' . htmlspecialchars($paramName) . '"'
            . ' ' . DATA_BASE_URL . '="' . htmlspecialchars($baseUrl) . '"';

        // Add count SIP if provided (for dynamic count refresh)
        if (!empty($countSip)) {
            $dataAttrs .= ' ' . DATA_COUNT_SIP . '="' . htmlspecialchars($countSip) . '"';
        }

        // Add label if provided (for display in dropdown header)
        if (!empty($label)) {
            $dataAttrs .= ' ' . DATA_LABEL . '="' . htmlspecialchars($label) . '"';
        }

        // Find elements with qfq-inline-filter class and add data attributes to them
        // This ensures data attributes are on the actual input element, not on wrappers
        $html = preg_replace(
            '/(class\s*=\s*["\'][^"\']*qfq-inline-filter[^"\']*["\'])/',
            '$1' . $dataAttrs,
            $html
        );

        return $html;
    }

    /**
     * Get the corresponding Renderer for a given FormElement
     *
     * @param AbstractFormElement $fe The FormElement for which the correct Renderer is needed.
     * @return FormElementRenderer|null
     * @throws \UserFormException
     */
    private function getFormElementRenderer(AbstractFormElement $fe): ?FormElementRenderer {
        switch (get_class($fe)) {
            case "IMATHUZH\Qfq\Core\Form\FormElement\ExtraFormElement":
                return null;
            case "IMATHUZH\Qfq\Core\Form\FormElement\NoteFormElement":
                return $this->noteRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\InputFormElement":
                return $this->inputRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\EditorFormElement":
                return $this->editorRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\DatetimeFormElement":
                return $this->datetimeRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\CheckboxFormElement":
                return $this->checkboxRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\RadioButtonFormElement":
                return $this->radioRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\SelectFormElement":
                return $this->selectRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\UploadFormElement":
                return $this->uploadRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\ImageCutFormElement":
                return $this->imageCutRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\AnnotateFormElement":
                return $this->annotateRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\SubrecordFormElement":
                return $this->subrecordRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\ChatFormElement":
                return $this->chatRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\PillFormElement":
                return $this->pillRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\FieldsetFormElement":
                return $this->fieldsetRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\TemplateGroupFormElement":
                return $this->templateGroupRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\ColorPickerFormElement":
                return $this->colorPickerRenderer;
            case "IMATHUZH\Qfq\Core\Form\FormElement\InputCollectionJsonFormElement":
                return $this->inputCollectionJsonRenderer;
            default:
                throw new \UserFormException("No Renderer Class found for " . get_class($fe));
        }
    }

    /**
     * Get all elements from STORE_ADDITIONAL_FORM_ELEMENTS and return them as a string.
     *
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     */
    private function renderAdditionalFormElements(): string {

        $data = $this->store->getStore(STORE_ADDITIONAL_FORM_ELEMENTS);
        return is_array($data) ? implode('', $data) : '';
    }

    /**
     * @param array $elementSpec
     * @param string $renderMode
     * @return void
     */
    public function renderElement(array $elementSpec, string $renderMode = RENDER_MODE_HTML) {

    }

    /**
     * Create HTML Input vars to detect bot automatic filling of forms.
     *
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     */
    protected function renderHoneypotElements(): string {
        $html = '';

        $vars = $this->store->getVar(SYSTEM_SECURITY_VARS_HONEYPOT, STORE_SYSTEM);

        // Iterate over all fake vars
        $arr = explode(',', $vars);
        foreach ($arr as $name) {
            $name = trim($name);
            if ($name === '') {
                continue;
            }
            $html .= "<input name='$name' type='hidden' value='' readonly>";
        }

        return $html;
    }

    /**
     * Build MD5 from the current record. Return HTML Input element.
     *
     * @param Form $form
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    protected function renderRecordHashMd5Input(Form $form): string {
        $md5 = $form->getRecordHashMd5();
        return "<input id='" . DIRTY_RECORD_HASH_MD5 . "' name='" . DIRTY_RECORD_HASH_MD5 . "' type='hidden' value='$md5'>";
    }

    /**
     * Copy a subset of current STORE_TYPO3 variables to SIP. Set a hidden form field to submit the assigned SIP to
     * save/update.
     *
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     */
    protected function renderHiddenT3Sip(): string {
        $t3VarsSip = $this->store->copyT3VarsToSip();
        return HelperFormElement::buildNativeHidden(CLIENT_TYPO3VARS, $t3VarsSip);
    }


    /**
     * @return string
     * @throws \CodeException
     * @throws \UserFormException
     */
    protected function renderHiddenSip(): string {

        $sipArray = $this->store->getStore(STORE_SIP);

        // do not include system vars
        unset($sipArray[SIP_SIP]);
        unset($sipArray[SIP_URLPARAM]);

        $queryString = Support::arrayToQueryString($sipArray);
        $sip = $this->store->getSipInstance();
        $sipValue = $sip->queryStringToSip($queryString, RETURN_SIP);

        $wrapSetupClass = $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] ?? '';
        //$json[] = $this->getFormElementForJson(CLIENT_SIP, $sipValue, [FE_MODE => FE_MODE_SHOW], $wrapSetupClass);

        return HelperFormElement::buildNativeHidden(CLIENT_SIP, $sipValue);
    }
}