<?php

namespace IMATHUZH\Qfq\Core\Renderer\FormElement\Bootstrap3;

use IMATHUZH\Qfq\Core\Database\DatabaseManager;
use IMATHUZH\Qfq\Core\Form\FormElement\AbstractFormElement;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Store;

class Bootstrap3SubrecordRenderer extends \IMATHUZH\Qfq\Core\Renderer\FormElement\Base\SubrecordRenderer {

    private ?Link $link = null;

    /**
     * @param AbstractFormElement $fe
     * @param string $renderMode
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function renderHtml(AbstractFormElement $fe, string $renderMode = RENDER_MODE_HTML): string {
        // Subrecord on Form with r=0
        if (isset($fe->attributes[SUBRECORD_MESSAGE])) return "<p>" . $fe->attributes[SUBRECORD_MESSAGE] . "</p>";


        $fe->htmlAttributes[CSS_CLASS] = implode(' ', $fe->cssClasses);
        $htmlAttributes = Support::arrayToXMLAttributes($fe->htmlAttributes);

        $sip = Store::getInstance()->getSipInstance();
        // Take Form.parameter.dbIndex if specified, else the default. Necessary for e.g. FormEditor: subrecords (FormElement) saved in QFQ System.
        $dbIndex = $fe->form->specFinal[F_DB_INDEX] ?? DatabaseManager::getInstance()->indexData;
        $this->link = new Link($sip, $dbIndex);

        $htmlHead = $this->renderHead($fe);
        $htmlBody = $this->renderBody($fe);


        //Add additional params to table itself
        $tableAttributes = "";
        if (isset($this->attributes[FE_SUBRECORD_TABLE_ATTRIBUTE])) {
            $tableAttributes = $fe->evaluate->parse($fe->attributes[FE_SUBRECORD_TABLE_ATTRIBUTE]);
        }
        $fe->attributes = HelperFormElement::prepareExtraButton($fe->attributes, true, 'caption-btn-group');
        if (isset($fe->attributes[FE_INPUT_EXTRA_BUTTON_LOCK]) && $fe->attributes[FE_MODE] !== FE_MODE_READONLY)  $fe->dndHtmlAttributes .= 'class="qfq-disabled"';

        // Subrecord Label ("Caption")
        $captionContent = Support::wrapTag('<div>', $fe->attributes[FE_TMP_EXTRA_BUTTON_HTML]);
        $caption = isset($fe->attributes[FE_LABEL]) ? '<caption class="qfq-subrecord-title"><div class="caption-flex">' . $fe->attributes[FE_LABEL] . $captionContent . '</div></caption>' : '';
        $tbody = Support::wrapTag("<tbody $fe->dndHtmlAttributes>", $htmlBody);
        $table = Support::wrapTag("<table $htmlAttributes $tableAttributes>", $caption . $htmlHead . $tbody, true);
        $table .= $this->renderFormEditorUrl($fe);
        $table .= HelperFormElement::getHelpBlock() . $fe->attributes[FE_INPUT_EXTRA_BUTTON_INFO];
        return Support::wrapTag('<div id="' . $fe->attributes[FE_HTML_ID] . '" class="form-group clearfix">', $table);

    }

    /**
     * Render the subrecord head (thead)
     *
     * @param AbstractFormElement $fe
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderHead(AbstractFormElement $fe): string {
        $linkNew = '';
        $checkboxAll = '';

        if ($fe->attributes[SUBRECORD_FLAG_CHECKBOX]) {
            $checkboxAttr = Support::doAttribute(HTML_ATTR_TYPE, HTML_ATTR_TYPE_CHECKBOX);
            $checkboxAttr .= Support::doAttribute(HTML_ATTR_CLASS,  [SYNC_BY_RULE_CLASS_QFQ_SYNCBYRULE_CHECKBOX_ALL, CLASS_SKIP_DIRTY]);
            $checkboxAttr .= Support::doAttribute(HTML_ATTR_TITLE, SYNC_BY_RULE_CHECKBOX_TITLE);
            $checkbox = "<input {$checkboxAttr}>";

            if ($fe->attributes[FE_SUBRECORD_COPY_RULE_IMPORT]
                || $fe->attributes[FE_SUBRECORD_COPY_RULE]) {

                // Import/Paste button
                if ($fe->attributes[FE_SUBRECORD_COPY_RULE_IMPORT]) {
                    if ($fe->attributes[FE_MODE] !== FE_MODE_READONLY) {
                        $importBtnAttr = Support::doAttribute(HTML_ATTR_DATA_SYNC_BY_RULE_TYPE, SYNC_BY_RULE_IMPORT);
                        $importBtnAttr .= Support::doAttribute(HTML_ATTR_DATA_SIP, $fe->checkboxesData[FE_SUBRECORD_COPY_RULE_IMPORT] ?? '');
                        $importBtnAttr .= Support::doAttribute(HTML_ATTR_DATA_BASE_URL, $fe->checkboxesData[SYSTEM_BASE_URL] ?? '');
                        $importBtnAttr .= Support::doAttribute(HTML_ATTR_CLASS, SYNC_BY_RULE_CLASS_QFQ_SYNCBYRULE);
                        $importBtnAttr .= Support::doAttribute(HTML_ATTR_TYPE, BTN_TYPE_BUTTON);
                        $importBtn = "<button {$importBtnAttr}>";
                        $importGlyphicon = Support::renderGlyphIcon(GLYPH_ICON_PASTE);
                        $importText = SYNC_BY_RULE_TEXT_PASTE;
                        $importBtn = Support::wrapTag($importBtn, "{$importGlyphicon} {$importText}");
                        $importBtn = Support::wrapTag('<li><a>', $importBtn);
                    }
                }

                // Export/Copy button
                if ($fe->attributes[FE_SUBRECORD_COPY_RULE]) {
                    $exportBtn = Support::insertAttribute('<button>', HTML_ATTR_CLASS, SYNC_BY_RULE_CLASS_QFQ_SYNCBYRULE_TRIGGER);
                    $exportGlyphicon = Support::renderGlyphIcon(GLYPH_ICON_COPY );
                    $exportText = SYNC_BY_RULE_TEXT_COPY;
                    $exportBtn = Support::wrapTag($exportBtn, "{$exportGlyphicon} {$exportText}");
                    $exportBtn = Support::wrapTag('<li><a>', $exportBtn);
                }

                // Dropdown
                $dropdownSpan = Support::insertAttribute('<span>', HTML_ATTR_CLASS, CLASS_PULL_RIGHT);
                $dropdownBtnGroup = Support::insertAttribute('<div>', HTML_ATTR_CLASS, CLASS_BTN_GROUP);
                $dropdownBtnAttr = Support::doAttribute(HTML_ATTR_TYPE, BTN_TYPE_BUTTON);
                $dropdownBtnAttr .= Support::doAttribute(HTML_ATTR_CLASS, [CLASS_DROPDOWN_TOGGLE, CLASS_BTN, CLASS_BTN_DEFAULT, CLASS_DROPDOWN_MENU_RIGHT]);
                $dropdownBtnAttr .= Support::doAttribute(HTML_ATTR_DATA_TOGGLE, 'dropdown');
                $dropdownBtnAttr .= Support::doAttribute(HTML_ATTR_ARIA_HASPOPUP, 'true');
                $dropdownBtnAttr .= Support::doAttribute(HTML_ATTR_ARIA_EXPANDED,  'false');
                $dropdownBtn = "<button {$dropdownBtnAttr}>";

                $dropdownGlyphicon = Support::renderGlyphIcon(GLYPH_ICON_OPTION_VERTICAL);
                $dropdownBtn = Support::wrapTag($dropdownBtn, $dropdownGlyphicon);

                $dropdownUl = Support::insertAttribute('<ul>', HTML_ATTR_CLASS, CLASS_DROPDOWN_MENU);
                $dropdownUl = Support::wrapTag($dropdownUl, $importBtn . $exportBtn);
                $dropdown = Support::wrapTag($dropdownSpan . $dropdownBtnGroup, $dropdownBtn . $dropdownUl);
                $clearfix = Support::insertAttribute('<span>', HTML_ATTR_CLASS, CLASS_CLEARFIX);
            }

            $checkboxAll = Support::wrapTag('<th data-sorter="false" class="filter-false" data-priority="always" style="width: 80px;">', $checkbox . $dropdown . $clearfix);
        }

        if ($fe->attributes[SUBRECORD_FLAG_NEW] || $fe->attributes[SUBRECORD_FLAG_EDIT]) {
            $buttonNew = '';
            if ($fe->attributes[SUBRECORD_FLAG_NEW]) {
                $buttonNew = $this->createFormLink($fe, 0, $fe->attributes[SUBRECORD_PRIMARY_RECORD], SYMBOL_NEW, 'New', $fe->attributes[SUBRECORD_PARAMETER_NEW] ?? '');
            }

            $newAndHeader = $buttonNew . $fe->attributes[SUBRECORD_COLUMN_TITLE_EDIT] ?? '';
            $linkNew = Support::wrapTag('<th data-sorter="false" class="filter-false" data-priority="always" style="width: 40px;">', $newAndHeader);
        }

        $columns = $checkboxAll . $linkNew;

        $firstRow = $fe->attributes[FE_SQL1][0] ?? array();

        if (!empty($firstRow)) {
            // construct column attributes
            $control = $this->getSubrecordColumnControl(array_keys($firstRow));

            foreach ($control[SUBRECORD_COLUMN_TITLE] as $columnName => $columnTitle) {
                if (($columnName[0] ?? '') != TOKEN_COLUMN_CTRL) {
                    $currentThWrap = '<th>';

                    if (!empty($control[SUBRECORD_COLUMN_TH_CLASS][$columnName])) {
                        $thClass = $control[SUBRECORD_COLUMN_TH_CLASS][$columnName];
                        $currentThWrap = "<th class='$thClass'>";
                    }

                    $columns .= Support::wrapTag($currentThWrap, $columnTitle);
                }
            }
        }

        if ($fe->attributes[SUBRECORD_FLAG_DELETE]) {
            $deleteTitle = $fe->attributes[SUBRECORD_COLUMN_TITLE_DELETE] ?? '';
            $columns .= "<th data-sorter='false' class='filter-false' data-priority='always' style='width: 40px;'>$deleteTitle</th>";
        }
        // Extra Button Lock
        $disabled = isset($fe->attributes[FE_INPUT_EXTRA_BUTTON_LOCK]) ? ' class="qfq-disabled"' : '';

        return Support::wrapTag("<thead $disabled><tr>", $columns);
    }

    /**
     * Render the subrecord body (tbody with all rows)
     *
     * @param AbstractFormElement $fe
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderBody(AbstractFormElement $fe): string {

        // Build general html rows
        $htmlBody = $this->buildSubrecordRowsHtml($fe);

        // Build additional html rows. Because of new rows html append to the end of general rows, dragAndDrop compatibility is limited to general rows.
        if (isset($fe->attributes[FE_SUBRECORD_APPEND_SQL])) {
            $htmlBody .= $this->buildSubrecordRowsHtml($fe, true);
        }
        return $htmlBody;
    }


    /**
     * Get various column format information based on the 'raw' column title. The attributes are separated by '|'
     * and specified as 'key' or 'key=value'.
     *
     * - Return all parsed values as an assoc array.
     * - For regular columns: If there is no 'width' specified, take the default 'SUBRECORD_COLUMN_WIDTH'
     * - For 'icon /  url / mailto': no width limit.
     *
     * Returned assoc array:
     *  title      Only key. Element is non numeric, which is not a keyword 'width/nostrip/icon/url/mailto'
     *  maxLength  Key/Value Pair. Not provided for 'icon/url/mailto'.
     *  nostrip    Only key. Do not strip HTML Tags from the content.
     *  icon       Only key. Value will rendered (later) as an image.
     *  url        Only key. Value will rendered (later) as a 'href'
     *  mailto     Only key. Value will rendered (later) as a 'href mailto'
     *
     * @param array $titleRaw
     * @return array
     * @throws \UserFormException
     */
    private function getSubrecordColumnControl(array $titleRaw) {
        $control = array();

        foreach ($titleRaw as $columnName) {

            switch ($columnName) {
                case SUBRECORD_COLUMN_ROW_CLASS:
                case SUBRECORD_COLUMN_ROW_TOOLTIP:
                case SUBRECORD_COLUMN_ROW_TITLE: // Backward compatibility
                    continue 2;
                default:
                    break;
            }

            $flagWidthLimit = true;
            $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = SUBRECORD_COLUMN_DEFAULT_MAX_LENGTH;

            // a) 'City|maxLength=40', b) 'Status|icon', c) 'Mailto@maxLength=80|nostrip'
            $arr = KeyValueStringParser::parse($columnName, '=', '|', KVP_IF_VALUE_EMPTY_COPY_KEY);
            foreach ($arr as $attribute => $value) {
                switch ($attribute) {
                    case SUBRECORD_COLUMN_MAX_LENGTH:
                    case SUBRECORD_COLUMN_NO_STRIP:
                    case SUBRECORD_COLUMN_TITLE:
                    case SUBRECORD_COLUMN_LINK:
                        break;
                    case SUBRECORD_COLUMN_ICON:
                    case SUBRECORD_COLUMN_URL:
                    case SUBRECORD_COLUMN_MAILTO:
                        $flagWidthLimit = false;
                        break;
                    default:
                        $attribute = is_numeric($value) ? SUBRECORD_COLUMN_MAX_LENGTH : SUBRECORD_COLUMN_TITLE;
                        break;
                }
                $control[$attribute][$columnName] = $value;
            }

            if (!isset($control[SUBRECORD_COLUMN_TITLE][$columnName])) {
                $control[SUBRECORD_COLUMN_TITLE][$columnName] = ''; // Fallback:  Might be wrong, but better than nothing.
            }

            // Don't render Columns starting with '_...'.
            if (substr($control[SUBRECORD_COLUMN_TITLE][$columnName], 0, 1) === '_') {
                unset($control[SUBRECORD_COLUMN_TITLE][$columnName]); // Do not render column later.
                continue;
            }

            // Limit title length
            $control[SUBRECORD_COLUMN_TITLE][$columnName] = mb_substr($control[SUBRECORD_COLUMN_TITLE][$columnName], 0, $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName]);

            if (!$flagWidthLimit) {
                $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = false;
            }
        }

        return $control;
    }


    /**
     * Build html for subrecord rows.
     * Customizable with own settings array.
     * <tr ...><td>...</td></tr><tr><td>...</td></tr>...
     *
     * @param AbstractFormElement $fe
     * @param $additionalRow
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \IMATHUZH\Qfq\Core\Exception\RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function buildSubrecordRowsHtml(AbstractFormElement $fe, $additionalRow = false): string {
        $htmlBody = '';
        $rowHtml = '';
        $subrecordToken = FE_SQL1;

        // Configure formElement array for additional rows.
        if ($additionalRow) {
            $fe->attributes = array_merge($fe->attributes, $this->createFeSubrecordAdditionalRow($fe->attributes));
            $subrecordToken = FE_SUBRECORD_APPEND_SQL;
        }

        foreach ($fe->attributes[$subrecordToken] as $row) {
            $rowHtml = '';
            if ($fe->attributes[FE_MODE] == FE_MODE_READONLY) {
                $editToolTip = 'Show';
                $editSymbol = SYMBOL_SHOW;
                $deleteRender = '|r:3';
            } else {
                $editToolTip = 'Edit';
                $editSymbol = SYMBOL_EDIT;
                $deleteRender = '';
            }

            // Checkboxes
            if ($fe->attributes[SUBRECORD_FLAG_CHECKBOX]) {
                $rowHtml .= Support::wrapTag('<td style="vertical-align: middle;">', ('<input class="qfq-skip-dirty" type="checkbox">'));
            }

            if ($fe->attributes[SUBRECORD_FLAG_EDIT]) {
                $rowHtml .= Support::wrapTag('<td style="vertical-align: middle;">',
                    $this->createFormLink($fe, $row[$fe->attributes[SUBRECORD_NAME_COLUMN_ID]], $fe->attributes[SUBRECORD_PRIMARY_RECORD], $editSymbol, $editToolTip, $row[SUBRECORD_COLUMN_ROW_EDIT] ?? '', $row));
            } elseif ($fe->attributes[SUBRECORD_FLAG_NEW]) {
                // Fake column
                $rowHtml .= Support::wrapTag('<td style="vertical-align: middle;">', $rowHtml, false);
            }

            // All columns
            foreach ($row as $columnName => $value) {
                if (isset($fe->attributes[SUBRECORD_CONTROL][SUBRECORD_COLUMN_TITLE][$columnName])) {
                    $rowHtml .= Support::wrapTag("<td>", $this->renderCell($fe->attributes[SUBRECORD_CONTROL], $columnName, $value, $fe->attributes[SUBRECORD_CSS_CLASS_COLUMN_ID]));
                }
            }

            if ($fe->attributes[SUBRECORD_FLAG_DELETE]) {
                // $strLink = 'x:a|b|s|U:form=' . $formName . '&r:' . $recordId;
                $strDefault = TOKEN_ACTION_DELETE . ':' . TOKEN_ACTION_DELETE_AJAX
                    . '|' . TOKEN_GLYPH . ':' . GLYPH_ICON_DELETE
                    . '|' . TOKEN_SIP . '|' . TOKEN_BOOTSTRAP_BUTTON . $deleteRender
                    . '|' . TOKEN_URL_PARAM . ':form=' . $fe->attributes[F_FINAL_DELETE_FORM] . '&r=' . $row[$fe->attributes[SUBRECORD_NAME_COLUMN_ID]]
                    . '|' . TOKEN_TOOL_TIP . ':Delete';
                $rowHtml .= Support::wrapTag('<td style="vertical-align: middle;">', $this->link->renderLink($row[SUBRECORD_COLUMN_ROW_DELETE] ?? '', $strDefault));
            }

            Support::setIfNotSet($row, SUBRECORD_COLUMN_ROW_CLASS);
            $rowClass = 'record ';
            $rowClass .= $row[SUBRECORD_COLUMN_ROW_CLASS];
            $defaultAdditionalRowClass = 'qfq-subrecord-additional';

            if ($additionalRow) {
                if (isset($fe->attributes[FE_SUBRECORD_APPEND_CLASS])) {
                    $defaultAdditionalRowClass = $fe->attributes[FE_SUBRECORD_APPEND_CLASS];
                }
                $rowClass .= ' ' . $defaultAdditionalRowClass;
            }

            $rowTooltip = '';
            if (isset($row[SUBRECORD_COLUMN_ROW_TOOLTIP])) {
                $rowTooltip = $row[SUBRECORD_COLUMN_ROW_TOOLTIP];
            } elseif (isset($row[SUBRECORD_COLUMN_ROW_TITLE])) { // backward compatibility
                $rowTooltip = $row[SUBRECORD_COLUMN_ROW_TITLE];
            }

            $rowAttribute = '';
            // Enable checkboxes
            if ($fe->attributes[SUBRECORD_FLAG_CHECKBOX]) {
                $rowClass .= ' qfq-syncByRule-element';
                $rowAttribute .= Support::doAttribute('data-rule', $fe->checkboxesData[$row[$fe->attributes[SUBRECORD_NAME_COLUMN_ID]]]);
            }

            $rowAttribute .= Support::doAttribute(HTML_ATTR_CLASS, $rowClass);
            $rowAttribute .= Support::doAttribute(HTML_ATTR_TITLE, $rowTooltip);
            if ($fe->attributes[SUBRECORD_HAS_DRAG_AND_DROP]) {
                $rowAttribute .= Support::doAttribute(HTML_ATTR_ID, $fe->attributes[FE_HTML_ID] . '-' . $row[$fe->attributes[SUBRECORD_NAME_COLUMN_ID]]);
                $rowAttribute .= Support::doAttribute('data-dnd-id', $row[$fe->attributes[SUBRECORD_NAME_COLUMN_ID]]);
            }
            $htmlBody .= Support::wrapTag("<tr $rowAttribute>", $rowHtml, true);
        }

        // TODO: New param subrecordEmpty from develop
        if (empty($fe->attributes[$subrecordToken])) {
            $rowHtml .= Support::wrapTag("<td colspan='100%'>", $fe->attributes[SUBRECORD_EMPTY_TEXT] ?? SUBRECORD_DEFAULT_EMPTY_TEXT);
            $htmlBody .= Support::wrapTag("<tr>", $rowHtml, true);
        }
        return $htmlBody;
    }


    /**
     * Create own formElement for subrecord additional rows to handle it separately.
     * Possible that additional rows have own form or delete form.
     *
     * @param array $formElement
     * @return array
     */
    private function createFeSubrecordAdditionalRow(array $formElement): array {
        $formElementAppendRows = array();
        $formElementAppendRows[FE_HTML_ID] = $formElement[FE_HTML_ID];
        $formElementAppendRows[FE_MODE] = $formElement[FE_MODE];
        $formElementAppendRows[FE_SUBRECORD_APPEND_SQL] = $formElement[FE_SUBRECORD_APPEND_SQL];

        // Using own class possible. If isset but empty, default class will be deactivated.
        if (isset($formElement[FE_SUBRECORD_APPEND_CLASS])) {
            $formElementAppendRows[FE_SUBRECORD_APPEND_CLASS] = $formElement[FE_SUBRECORD_APPEND_CLASS];
        }

        if (isset($formElement[SUBRECORD_PARAMETER_PAGE])) {
            $formElementAppendRows[SUBRECORD_PARAMETER_PAGE] = $formElement[SUBRECORD_PARAMETER_PAGE];
        }

        if (isset($formElement[SUBRECORD_PARAMETER_DETAIL])) {
            $formElementAppendRows[SUBRECORD_PARAMETER_DETAIL] = $formElement[SUBRECORD_PARAMETER_DETAIL];
        }

        // Using own form possible.
        if (isset($formElement[FE_SUBRECORD_APPEND_FORM])) {
            $formElementAppendRows[SUBRECORD_PARAMETER_FORM] = $formElement[FE_SUBRECORD_APPEND_FORM];
        } else {
            $formElementAppendRows[SUBRECORD_PARAMETER_FORM] = $formElement[SUBRECORD_PARAMETER_FORM];
        }

        // Using own delete form possible.
        if (isset($formElement[FE_SUBRECORD_APPEND_EXTRA_DELETE_FORM])) {
            $formElementAppendRows[F_FINAL_DELETE_FORM] = $formElement[FE_SUBRECORD_APPEND_EXTRA_DELETE_FORM];
        } else {
            $formElementAppendRows[F_FINAL_DELETE_FORM] = $formElementAppendRows[SUBRECORD_PARAMETER_FORM];
        }

        return $formElementAppendRows;
    }


    /**
     * Renders a Link with a symbol (edit/new) and register a new SIP to grant permission to the link.
     *
     * Returns <a href="<Link>">[icon]</a>
     *
     * Link: <page>?s=<SIP>&<standard typo3 params>
     * SIP: form = $formElement['form'] (provided via formElement[FE_PARAMETER])
     *      r = $targetRecordId
     *      Parse  $formElement['detail'] with possible key/value pairs. E.g.: detail=id:gr_id,#{{a}}:p_id,#12:x_id
     *        gr_id = <<primarytable.id>>
     *        p_id = <<variable defined in SIP or Client>>
     *        x_id = 12 (constant)
     *
     * $strLink - Optional. Only used if custom parameter (e.g. in subrecord '_rowEdit', or fe.parameter.new) should be set.
     *                      Will overwrite all other settings (exception: url default/explicit params are merged).
     *
     * @param AbstractFormElement $fe
     * @param $targetRecordId
     * @param array $primaryRecord
     * @param $symbol
     * @param $toolTip
     * @param $strLink
     * @param $currentRow
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \IMATHUZH\Qfq\Core\Exception\RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function createFormLink(AbstractFormElement $fe, $targetRecordId, array $primaryRecord, $symbol, $toolTip, $strLink, $currentRow = array()): string {

        $queryStringArray = [
            SIP_FORM => $fe->attributes[SUBRECORD_PARAMETER_FORM],
            SIP_RECORD_ID => $targetRecordId,
            PARAM_DB_INDEX_DATA => $fe->form->specFinal[F_DB_INDEX],
        ];

        // Inherit current F_MODE:
        if ($fe->form->specFinal[F_MODE_GLOBAL] != '') {
            $queryStringArray[F_MODE_GLOBAL] = $fe->form->specFinal[F_MODE_GLOBAL];
        }

        // In case the subrecord FE is set to 'readonly': subforms will be called with formModeGlobal=readonly
        if ($fe->attributes[FE_MODE] == FE_MODE_READONLY) {
            $queryStringArray[F_MODE_GLOBAL] = FE_MODE_READONLY;
        }

        // Add custom query parameter
        if (isset($fe->attributes[SUBRECORD_PARAMETER_DETAIL])) {

            // detail = pId:yId,&{{var:C}}:zId -> [ [ "pId" => "yId" ], [ "&{{var:C}}" => "zId"] ]
            $detailParam = KeyValueStringParser::parse($fe->attributes[SUBRECORD_PARAMETER_DETAIL], PARAM_KEY_VALUE_DELIMITER, PARAM_LIST_DELIMITER, KVP_VALUE_GIVEN, KVP_MODE_ALL);

            foreach ($detailParam as $key => $detail) {
                if (is_array($detail)) {
                    // Format: [['srcField' => 'destField']]
                    $src = array_key_first($detail);
                    $dest = $detail[$src];
                } else {
                    // Format: ['src' => 'dest']
                    $src = $key;
                    $dest = $detail;
                }

                // Constants
                if (($src[0] ?? '') == '&') {
                    $queryStringArray[$dest] = substr($src, 1);
                    continue;
                }
                // Form record values or parameter
                if (isset($primaryRecord[$src])) {
                    $queryStringArray[$dest] = $primaryRecord[$src];
                    continue;
                }

                // Current row - check '$src' and  '_$src' )
                foreach (['', '_'] as $pre) {
                    if (isset($currentRow[$pre . $src])) {
                        $queryStringArray[$dest] = $currentRow[$pre . $src];
                        continue 2;
                    }
                }

                // None of the above matched: poor solution of error reporting
                $queryStringArray[$dest] = ERROR_SUBRECORD_DETAIL_COLUMN_NOT_FOUND;
            }
        }

        // Check if detail contains Typo3 reserved keywords. Especially 'id' is forbidden at this state
        if (isset($queryStringArray[CLIENT_PAGE_ID]) || isset($queryStringArray[CLIENT_PAGE_TYPE]) || isset($queryStringArray[CLIENT_PAGE_LANGUAGE])) {
            throw new \UserFormException("Reserved Typo3 keyword (id, type, L) in formElement.parameter.detail - please use something else.");
        }

        Support::appendTypo3ParameterToArray($queryStringArray);
        // If there is a specific targetpage defined, take it.
        if (isset($fe->attributes[SUBRECORD_PARAMETER_PAGE]) && $fe->attributes[SUBRECORD_PARAMETER_PAGE] !== '') {
            $queryStringArray[CLIENT_PAGE_ID] = $fe->attributes[SUBRECORD_PARAMETER_PAGE];
        }

        $queryString = Support::arrayToQueryString($queryStringArray);

        // Define defaults
        $strDefault = 'p:' . $queryString . '|b|s|o:' . $toolTip . '|G:' . $symbol;

        // In case $strLink is given: copy computed urlParam to $strLink, than the App-Developer do not have to implent
        // subrecord.detail manually in subrecord.sql1
        $strLink = $this->copyDefaultUrlParamToExplicit($queryStringArray, $strLink);

        return $this->link->renderLink($strLink, $strDefault);
    }

    /**
     * If exist" get TOKEN_PAGE from $strLink, explode and merge with $queryStringArray. Return result as string.
     *
     * @param $queryStringArray
     * @param $strLink
     * @return string
     * @throws \UserFormException
     */
    private function copyDefaultUrlParamToExplicit($queryStringArray, $strLink): string {

        // If it is empty, then the default will be used: nothing to do here.
        if (empty($strLink)) {
            return '';
        }

        $arrStrLink = KeyValueStringParser::parse($strLink, PARAM_KEY_VALUE_DELIMITER, PARAM_DELIMITER);
        // If it is empty, then the default will be used: nothing to do here.
        if (empty($arrStrLink[TOKEN_PAGE])) {
            return $strLink;
        }

        // Explode explicit given url params: id=123&pId=45&r=56&form=address
        $arr = KeyValueStringParser::parse($arrStrLink[TOKEN_PAGE], '=', '&');

        // Merge default and explicit given. Explicit should overwrite default
        $arrStrLink[TOKEN_PAGE] = Support::arrayToQueryString(array_merge($queryStringArray, $arr));

        // Implode $strLink again.
        return KeyValueStringParser::unparse($arrStrLink, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
    }

    /**
     * Renders $value as specified in array $control
     *
     * nostrip: by default, HTML tags are removed. With this attribute, the value will be delivered as it is.
     * width: if there is a size limit - apply it.
     * icon: The cell will be rendered as an image. $value should contain the name of an image in
     * 'fileadmin/icons/'
     * mailto: The cell will be rendered as an <a> tag with the 'mailto' attribute.
     * url:  The cell will be rendered as an <a> tag. The value will be exploded by '|'. $value[0] = href, value[1]
     * = text. E.g. $value = 'www.math.uzh.ch/?id=45&v=234|Show details for Modul 123' >> <a
     * href="www.math.uzh.ch/?id=45&v=234">Show details for Modul 123</a>
     *
     * @param array $control
     * @param $columnName
     * @param $columnValue
     * @param $cssClassColumnId
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \IMATHUZH\Qfq\Core\Exception\RedirectResponse
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function renderCell(array $control, $columnName, $columnValue, $cssClassColumnId): string {

        if(is_null($columnValue)){
            $columnValue='';
        }

        switch ($columnName) {
            // Skip columns with special names
            case SUBRECORD_COLUMN_ROW_CLASS:
            case SUBRECORD_COLUMN_ROW_TITLE:
            case SUBRECORD_COLUMN_ROW_EDIT:
            case SUBRECORD_COLUMN_ROW_DELETE:
                return '';
            default:
                break;
        }

        $arr = explode('|', (string)$columnValue);
        if (count($arr) == 1) {
            $arr[1] = $arr[0];
        }

        if (isset($control[SUBRECORD_COLUMN_NO_STRIP][$columnName])) {
            $cell = $columnValue;
            $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] = false;
        } else {
            $cell = strip_tags((string)$columnValue);
        }

        if ($control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] !== false && $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName] != 0) {
            $cell = mb_substr($cell, 0, $control[SUBRECORD_COLUMN_MAX_LENGTH][$columnName]);
        }

        if (isset($control[SUBRECORD_COLUMN_ICON][$columnName])) {
            $cell = ($cell === '') ? '' : "<image src='" . Path::urlExt(Path::EXT_TO_PATH_ICONS, $cell) . "'>";
        }

        if (isset($control[SUBRECORD_COLUMN_MAILTO][$columnName])) {
            $cell = "<a " . Support::doAttribute('href', "mailto:$arr[0]") . ">$arr[1]</a>";
        }

        if (isset($control[SUBRECORD_COLUMN_URL][$columnName])) {
            $cell = "<a " . Support::doAttribute('href', $arr[0]) . ">$arr[1]</a>";
        }

        if (isset($control[SUBRECORD_COLUMN_LINK][$columnName])) {
            $cell = $this->link->renderLink($columnValue);
        }

        if (strcasecmp($columnName, 'id') == 0) {
            $cell = Support::wrapTag('<span class="' . $cssClassColumnId . '">', $cell, true);
        }
        return $cell;
    }
}