<?php

namespace IMATHUZH\Qfq\Core\Form\FormElement;

use IMATHUZH\Qfq\Core\Evaluate;
use IMATHUZH\Qfq\Core\Form\Form;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;

class EditorFormElement extends AbstractFormElement {
    public array $htmlAttributesCharacterCount;

    public function __construct($attributes, ?Form $form = null) {
        parent::__construct($attributes, $form);

        if (empty($this->attributes[FE_EDITOR_TYPE])) {
            $this->attributes[FE_EDITOR_TYPE] = FE_EDITOR_TYPE_TINYMCE;
        }


        switch ($this->attributes[FE_EDITOR_TYPE]) {
            case FE_EDITOR_TYPE_TINYMCE:
                $this->handleTinyMCE();
                break;
            case FE_EDITOR_TYPE_CODEMIRROR:
                $this->handleCodeMirror();
                break;
            default:
                throw new \UserFormException("Unexpected editor type: '" . $this->attributes[FE_EDITOR_TYPE] . "'", ERROR_FORMELEMENT_EDITOR_TYPE);
        }
    }

    /**
     * @return void
     * @throws \UserFormException
     */
    private function handleCodeMirror(): void {
        $this->htmlAttributes[ATTRIBUTE_DATA_REFERENCE] = $this->attributes[FE_DATA_REFERENCE];
        if ($this->attributes[FE_DYNAMIC_UPDATE] == 'yes') $this->htmlAttributes[FE_DATA_LOAD] = FE_DATA_LOAD;

        array_push($this->cssClasses, 'qfq-codemirror');

        // Defaults: fits good to QFQ style programming
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_MODE] = $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_MODE] ?? CODEMIRROR_MODE_QFQ;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_LINE_NUMBERS] = $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_LINE_NUMBERS] ?? true;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_LINE_WRAPPING] = $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_LINE_WRAPPING] ?? true;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_TAB_SIZE] = $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_TAB_SIZE] ?? 2;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_STYLE_ACTIVE_LINE] = $formElement[FE_EDITOR_PREFIX . CODEMIRROR_STYLE_ACTIVE_LINE] ?? false;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_MATCH_BRACKETS] = $formElement[FE_EDITOR_PREFIX . CODEMIRROR_MATCH_BRACKETS] ?? true;
        $this->attributes[FE_EDITOR_PREFIX . CODEMIRROR_AUTO_CLOSE_BRACKETS] = $formElement[FE_EDITOR_PREFIX . CODEMIRROR_AUTO_CLOSE_BRACKETS] ?? true;

        if ($this->attributes[FE_MODE] == FE_MODE_READONLY) {
            $this->attributes[FE_EDITOR_PREFIX . FE_EDITOR_READ_ONLY] = true;
        }

        $json = $this->getPrefixedElementsAsJSON(FE_EDITOR_PREFIX, $this->attributes);
        $this->htmlAttributes['data-config'] = htmlentities($json);

        $minMax = explode(',', $this->attributes[FE_SIZE], 2);
        if (isset($minMax[0]) && $minMax[0] !== '') {
            $this->htmlAttributes['data-height'] = $minMax[0];
        }

    }


    /**
     * @return void
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function handleTinyMCE(): void {
        if (isset($this->htmlAttributes[ATTRIBUTE_DISABLED])) {
            array_push($this->cssClasses, 'qfq-tinymce readonly');
        } else {
            array_push($this->cssClasses, 'qfq-tinymce');
        }
        $secureUpload = '';
        $this->htmlAttributes['data-control-name'] = $this->htmlAttributes[HTML_ATTR_NAME];
        $this->htmlAttributes['data_placeholder'] = $this->attributes[FE_PLACEHOLDER];
        if ($this->attributes[FE_DYNAMIC_UPDATE] === 'yes') $this->htmlAttributes[FE_DATA_LOAD] = FE_DATA_LOAD;

        $this->setEditorTinyMCEConfig();

        // AI assist: data attributes + SIP on the textarea, one 'AI' dropdown in the toolbar. The menu is registered client side (tinyMCE.js) only if
        $this->handleAiAssist(false);
        if (isset($this->htmlAttributes[DATA_AI_ASSIST])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] .= ' | qfqAi';
        }

        // Get standard upload filepath from user and convert to SIP
        if (isset($this->attributes[FE_EDITOR_FILE_UPLOAD_PATH]) && $this->attributes[FE_EDITOR_FILE_UPLOAD_PATH] != '') {
            $base64Param = base64_encode($this->attributes[FE_EDITOR_FILE_UPLOAD_PATH]);
            if (str_contains($this->attributes[FE_EDITOR_FILE_UPLOAD_PATH], PROTECTED_UPLOAD_DIR)) {
                $secureUpload = '&' . FE_TINYMCE_SECURE_IMAGE_UPLOAD . '=1';
                $this->value = OnString::replaceImageSourceWithSip($this->value);
            }
            $urlParam = 'fileUploadPath=' . $base64Param . $secureUpload;
            $sipParam = $this->sip->queryStringToSip($urlParam, RETURN_SIP);
            $completeUrl = '?action=imageUpload&s=' . $sipParam;
        } else {
            $completeUrl = '?action=imageUpload';
        }

        // Prepare maxLength and characterCount for tinyMce
        $maxLength = $this->attributes[FE_MAX_LENGTH];
        $this->htmlAttributesCharacterCount = array();
        if (isset($this->attributes[FE_CHARACTER_COUNT_WRAP])) {
            $this->htmlAttributes['data-character-count-id'] = $this->attributes[FE_HTML_ID] . HTML_ID_EXTENSION_CHARACTER_COUNT;
            $this->htmlAttributesCharacterCount[HTML_ATTR_ID] = $this->attributes[FE_HTML_ID] . HTML_ID_EXTENSION_CHARACTER_COUNT;
            $this->htmlAttributesCharacterCount['class'] = $this->attributes[FE_CHARACTER_COUNT_WRAP] == '' ? 'qfq-cc-style' : '';
        }

        // Get baseUrl for image upload. Path handling in T3 => V10 needs absolute path.
        $baseUrl = $this->store::getVar(SYSTEM_BASE_URL, STORE_SYSTEM);

        //TODO: static setup for TinyMCE ImagePlugin - needs to be activated / dynamically set by QFQ parameter.
        $preSettings = [
#            "plugins" => "image",
            "plugins" => "charmap",
            "maxLength" => $maxLength,
            "file_picker_types" => "file image media",
            "image_advtab" => true,
            "automatic_uploads" => true,
            "images_upload_url" => $baseUrl . Path::appToApi(API_FILE_PHP) . $completeUrl,
            "images_reuse_filename" => true,
            "paste_data_images" => true
        ];

        // TODO: JSON Stuff
        $json = $this->getPrefixedElementsAsJSON(FE_EDITOR_PREFIX, $this->attributes, $preSettings);
        $this->htmlAttributes['data-config'] = htmlentities($json);
    }


    /**
     * Searches for '$prefix*' elements in $formElement. Collect all found elements, strip $prefix (=$keyName) and
     *   returns keys/values JSON encoded.
     * Only 'alpha' chars are allowed as keyName.
     * Empty $settings are ok.
     *
     * @param string $prefix
     * @param array $formElement
     * @param array $preSettings
     * @return false|string
     * @throws \UserFormException
     */
    private function getPrefixedElementsAsJSON($prefix, array $formElement, array $preSettings = array()): bool|string {
        $settings = array();
        $lengthPrefix = strlen($prefix);

        // E.g.: $key = editor-plugins
        foreach ($formElement as $key => $value) {
            if (substr($key, 0, $lengthPrefix) == $prefix) {

                $keyName = substr($key, $lengthPrefix);

                if ($keyName == '') {
                    throw new \UserFormException("Empty '" . $prefix . "*' keyname: '" . $keyName . "'", ERROR_INVALID_EDITOR_PROPERTY_NAME);
                }

                // $value might be boolean false, which should be used! Do not compare with ''.
                if (isset($value)) {
                    // real boolean are important for TinyMCE config 'statusbar', 'menubar', ...
                    if ($value === 'false') $value = false;
                    if ($value === 'true') $value = true;
                    $settings[$keyName] = $value;
                }
            }
        }

        return json_encode(array_merge($preSettings, $settings));
    }

    /**
     * Parse $formElement[FE_EDITOR_*] settings and build editor settings.
     *
     * @return array|null
     */
    private function setEditorTinyMCEConfig(): ?array {
        // plugins
        if (!isset($this->attributes[FE_EDITOR_PREFIX . 'plugins'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'plugins'] = 'code link lists searchreplace table visualchars visualblocks image fullscreen';
        }

        // toolbar
        if (!isset($this->attributes[FE_EDITOR_PREFIX . 'toolbar'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] = 'code searchreplace undo redo fontsize | ' .
                'styles forecolor backcolor | ' .
                'bullist numlist outdent indent | link table | fullscreen';

            // Font Size Options
            $this->attributes[FE_EDITOR_PREFIX . 'font_size_formats'] = "8pt 10pt 12pt 14pt 18pt 24pt 36pt";
        }

        if (isset($this->attributes[FE_INPUT_EXTRA_BUTTON_LOCK])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] = 'extraLock ' . $this->attributes[FE_EDITOR_PREFIX . 'toolbar'];
        }

        if (isset($this->attributes[FE_INPUT_EXTRA_BUTTON_INFO])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] = 'extraInfo ' . $this->attributes[FE_EDITOR_PREFIX . 'toolbar'];
        }

        if (isset($this->attributes[FE_INPUT_EXTRA_BUTTON_LOCK])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] .= ' extraLock';
        }

        if (isset($this->attributes[FE_INPUT_EXTRA_BUTTON_INFO])) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] .= ' extraInfo';
        }

        // menubar
        if (!isset($this->attributes[FE_EDITOR_PREFIX . 'menubar'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'menubar'] = 'false';
        }

        // statusbar
        if (!isset($this->attributes[FE_EDITOR_PREFIX . 'statusbar'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'statusbar'] = 'false';
        }

        // autofocus
        if (isset($this->attributes[FE_AUTOFOCUS]) && $this->attributes[FE_AUTOFOCUS] == 'yes') {
            $this->attributes[FE_EDITOR_PREFIX . 'auto_focus'] = $this->htmlAttributes[HTML_ATTR_NAME];
        }

        // Image Classes config
        if(!isset($this->attributes[FE_EDITOR_PREFIX . 'image_class_list'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'image_class_list'] = [
                [FE_EDITOR_IMAGE_CONFIG_TITLE => 'No Style', FE_EDITOR_IMAGE_CONFIG_VALUE => 'img-auto-tinyMCE'],
                [FE_EDITOR_IMAGE_CONFIG_TITLE => 'Small (50%)', FE_EDITOR_IMAGE_CONFIG_VALUE => 'img-small-tinyMCE'],
                [FE_EDITOR_IMAGE_CONFIG_TITLE => 'Medium (75%)', FE_EDITOR_IMAGE_CONFIG_VALUE => 'img-medium-tinyMCE'],
                [FE_EDITOR_IMAGE_CONFIG_TITLE => 'Original (100%)', FE_EDITOR_IMAGE_CONFIG_VALUE => 'img-full-tinyMCE'],
                [FE_EDITOR_IMAGE_CONFIG_TITLE => 'Large (125%)', FE_EDITOR_IMAGE_CONFIG_VALUE => 'img-large-tinyMCE']
            ];
        }

        // Style added to TinyMCE Scope
        $this->attributes[FE_EDITOR_PREFIX . 'content_style'] = '.img-small-tinyMCE { width: 50%; height: auto; }
                                                                 .img-medium-tinyMCE { width: 75%; height: auto; }
                                                                 .img-large-tinyMCE { width: 125%; height: auto; }
                                                                 .img-full-tinyMCE { width: 100%; height: auto; }
                                                                 .img-auto-tinyMCE { }';

        // Default Image Class
        if (isset($this->attributes[FE_EDITOR_DEFAULT_IMAGE_CLASS])) {
            $class = match (strtolower($this->attributes[FE_EDITOR_DEFAULT_IMAGE_CLASS])) {
                'small' => 'img-small-tinyMCE',
                'medium' => 'img-medium-tinyMCE',
                'large' => 'img-large-tinyMCE',
                default => 'img-auto-tinyMCE',
            };
            $this->htmlAttributes[FE_EDITOR_DEFAULT_IMAGE_CLASS] = $class;
        }

        // Default Image Border
        if (isset($this->attributes[FE_EDITOR_PREFIX . FE_EDITOR_DEFAULT_IMAGE_BORDER])) {
            $this->htmlAttributes[FE_EDITOR_DEFAULT_IMAGE_BORDER] = $this->attributes[FE_EDITOR_PREFIX . FE_EDITOR_DEFAULT_IMAGE_BORDER];
        }

        // valid elements
        // Set defaults for tinyMce
        $imgToken = 'img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align]';
        $textDecoration = 'span[style]';
        $table = 'table[style|align|border],td[style],th[style],tr[style],tbody[style],thead[style]';
        $url = 'a[href|target|title]';
        $paragraphToken = 'p[align]';
        $strong = 'strong';

        $htmlAllowArray = array();

        if (isset($this->attributes[FE_HTML_ALLOW])) {
            $htmlAllowArray = explode(',', $this->attributes[FE_HTML_ALLOW]);
        }

        $formatDropdownElements = array();
        $customEditorToolbar = 'code |';

        // flags to prevent multiple same values
        $listFlag = false;
        $decorationFlag = false;
        $formatDropdownFlag = false;
        $customEditorToolbarFlags = array();

        foreach ($htmlAllowArray as $htmlToken => $value) {
            switch ($value) {
                case 'a':
                    $htmlAllowArray[$htmlToken] = $url;
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'link');
                    break;
                case 'table':
                    $htmlAllowArray[$htmlToken] = $table;
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'table');
                    break;
                case 'textDecoration':
                case 'u':
                case 'ins':
                case 's':
                case 'del':
                    if (!$decorationFlag) {
                        $htmlAllowArray[$htmlToken] = $textDecoration;
                        $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'textDecoration', 'underline strikethrough');
                        $decorationFlag = true;
                    }
                    break;
                case 'img':
                    $htmlAllowArray[$htmlToken] = $imgToken;
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'image');
                    break;
                case 'ul':
                    if (!$listFlag) {
                        $htmlAllowArray[$htmlToken] = 'ul,li';
                        $listFlag = true;
                    } else {
                        $htmlAllowArray[$htmlToken] = 'ul';
                    }
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'bullist');
                    break;
                case 'ol':
                    if (!$listFlag) {
                        $htmlAllowArray[$htmlToken] = 'ol,li';
                        $listFlag = true;
                    } else {
                        $htmlAllowArray[$htmlToken] = 'ol';
                    }
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'numlist');
                    break;
                case 'b':
                case 'strong':
                    $htmlAllowArray[$htmlToken] = $strong;
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'bold');
                    break;
                case 'i':
                case 'em':
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'italic');
                    break;
                case 'sub':
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'subscript');
                    break;
                case 'sup':
                    $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'supscript');
                    break;
                case 'h1':
                case 'h2':
                case 'h3':
                case 'h4':
                case 'h5':
                case 'h6':
                case 'p':
                case 'div':
                case 'pre':
                    if ($value === 'p') {
                        $htmlAllowArray[$htmlToken] = $paragraphToken;
                    }
                    $this->setTinymceBlockFormats($value, $formatDropdownElements);
                    $formatDropdownFlag = true;
                    break;
                default:
                    break;
            }
        }

        // set format dropdown at the end of the toolbar if its used
        if ($formatDropdownFlag) {
            $this->setTinymceEditorToolbarAttributes($customEditorToolbarFlags, $customEditorToolbar, 'formatselect');
        }

        // If htmlAllow is used: toolbar will be overwritten with the customized one
        if (!empty($customEditorToolbarFlags)) {
            $this->attributes[FE_EDITOR_PREFIX . 'toolbar'] = $customEditorToolbar;
        }

        // Set allowed values and corrected dropdown from formats
        if (isset($this->attributes[FE_HTML_ALLOW])) {
            $this->attributes[FE_EDITOR_PREFIX . 'valid_elements'] = implode(',', $htmlAllowArray);
            $this->attributes[FE_TINYMCE_DROPDOWN_FORMATS] = implode(';', $formatDropdownElements);
            $this->attributes[FE_EDITOR_PREFIX . 'block_formats'] = $this->attributes[FE_TINYMCE_DROPDOWN_FORMATS];
        }

        // Check for min_height, max_height
        $minMax = explode(',', $this->attributes[FE_SIZE], 2);
        if (isset($minMax[0]) && ctype_digit($minMax[0]) && !isset($this->attributes[FE_EDITOR_PREFIX . 'min_height'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'min_height'] = $minMax[0];
        }
        if (isset($minMax[1]) && ctype_digit($minMax[1]) && !isset($this->attributes[FE_EDITOR_PREFIX . 'max_height'])) {
            $this->attributes[FE_EDITOR_PREFIX . 'max_height'] = $minMax[1];
        }

        return $this->attributes;
    }


    /**
     * Tinymce toolbar attributes needs to be defined separately
     *
     * @param $toolbarFlags
     * @param $attributeList
     * @param $attributeName
     * @param $specialAttributeName
     * @return void
     */
    private function setTinymceEditorToolbarAttributes(&$toolbarFlags, &$attributeList, $attributeName, $specialAttributeName = ''): void {
        if (!empty($toolbarFlags[$attributeName]) && !$toolbarFlags[$attributeName]) {
            if ($specialAttributeName === '') {
                $attributeList .= ' ' . $attributeName;
            } else {
                $attributeList .= ' ' . $specialAttributeName;
            }
            $toolbarFlags[$attributeName] = true;
        }
    }


    /**
     * Tinymce has individual strings for some html block configurations. They need to be set here.
     *
     * @param $format
     * @param $formatDropdownElements
     * @return void
     */
    private function setTinymceBlockFormats($format, &$formatDropdownElements): void {

        switch ($format) {
            case 'h1':
                $formatDropdownElements[] = 'Heading 1=h1';
                break;
            case 'h2':
                $formatDropdownElements[] = 'Heading 2=h2';
                break;
            case 'h3':
                $formatDropdownElements[] = 'Heading 3=h3';
                break;
            case 'h4':
                $formatDropdownElements[] = 'Heading 4=h4';
                break;
            case 'h5':
                $formatDropdownElements[] = 'Heading 5=h5';
                break;
            case 'h6':
                $formatDropdownElements[] = 'Heading 6=h6';
                break;
            case 'p':
                $formatDropdownElements[] = 'Paragraph=p';
                break;
            case 'div':
                $formatDropdownElements[] = 'Div-Container=div';
                break;
            case 'pre':
                $formatDropdownElements[] = 'Preformat=pre';
                break;
            default:
                break;
        }
    }
}