<?php
/**
 * Created by PhpStorm.
 * User: proess
 * Date: 2/06/23
 * Time: 16:20 PM
 */

namespace IMATHUZH\Qfq\Core\Report;

use CodeException;
use DbException;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\SqlQuery;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
use UserFormException;
use UserReportException;

/**
 * Class InlineEdit
 * @package qfq
 */
class InlineEdit {
    /**
     * @var Database
     */
    private Database $database;
    /**
     * @var Store|null
     */
    private ?Store $store = null;
    /**
     * @var Link
     */
    private Link $link;
    /**
     * @var string|null
     */
    private ?string $table = null;
    /**
     * @var string|null
     */
    private ?string $column = null;
    /**
     * @var string|null
     */
    private ?string $feType = null;
    /**
     * @var string|null
     */
    private ?string $recordId = null;
    /**
     * @var string|null
     */
    private ?string $value = null;
    /**
     * @var string|null
     */
    private ?string $formName = null;
    /**
     * @var string|null
     */
    private ?string $formId = null;
    /**
     * @var string|null
     */
    private ?string $pageId = null;

    /**
     * InlineEdit constructor.
     * Initializes a new instance of the InlineEdit class.
     *
     * @throws UserFormException
     * @throws CodeException
     * @throws UserReportException
     */
    public function __construct(Database $database) {
        $this->store = Store::getInstance();
        $this->database = $database;
        $this->link = new Link(new Sip());
    }

    /**
     * Process the given parameters and map them to the corresponding properties of the InlineEdit object.
     * Example: 'table=Dummy&column=text1&type=text&r=1' or 'form=ipa&fe=text1&r=1'
     *
     * @param string $parameters The parameters to process.
     *
     * @throws UserFormException
     * @throws CodeException
     * @throws DbException
     * @throws UserReportException
     */
    public function process(string $parameters) {

        // Convert parameters from string to array
        $parsedParameters = KeyValueStringParser::explodeKvpSimple($parameters, '=', '&');

        // Check if 'form', 'fe' and 'r' keys exist
        if (array_key_exists(INLINE_EDIT_FORM, $parsedParameters)
            && array_key_exists(INLINE_EDIT_FORM_ELEMENT, $parsedParameters)
            && array_key_exists(INLINE_EDIT_RECORD_ID, $parsedParameters)) {

            // Set properties from parsedParameters
            $this->formName = $parsedParameters[INLINE_EDIT_FORM];

            // Get tableName based on formName
            $sql = SqlQuery::selectFormByName($this->formName);
            $form = $this->database->sql($sql[0], ROW_EXPECT_0_1, $sql[1]);

            // Check if sql query found a form
            if (!isset($form[INLINE_EDIT_TABLE_NAME])) {
                throw new \UserReportException ('Form "' . $parsedParameters[INLINE_EDIT_FORM] . '" does not exist.'
                    , ERROR_INLINE_EDIT_FORM_NONEXISTENT
                );
            }

            // Set properties from parsedParameters
            $this->formId = $form[F_ID];
            $this->table = $form[INLINE_EDIT_TABLE_NAME];
            $this->column = $parsedParameters[INLINE_EDIT_FORM_ELEMENT];
            $this->recordId = $parsedParameters[INLINE_EDIT_RECORD_ID];

            // Get formElement to get its type based on column and formName
            $sql = SqlQuery::selectFormElementByName($this->column, $this->formName);
            $formElement = $this->database->sql($sql[0], ROW_EXPECT_0_1, $sql[1]);

            // Check if sql query found a formElement
            if (!isset($formElement[FE_TYPE])) {
                throw new \UserReportException ('FormElement "'
                    . $parsedParameters[INLINE_EDIT_FORM_ELEMENT]
                    . '" does not exist on given Form "'
                    . $this->formName . '".'
                    , ERROR_INLINE_EDIT_FORM_ELEMENT_NONEXISTENT
                );
            }

            $this->feType = $formElement[FE_TYPE];

            // Check if 'table', 'column', 'type' and 'r' keys exist
        } elseif (array_key_exists(INLINE_EDIT_TABLE, $parsedParameters)
            && array_key_exists(INLINE_EDIT_COLUMN, $parsedParameters)
            && array_key_exists(FE_TYPE, $parsedParameters)
            && array_key_exists(INLINE_EDIT_RECORD_ID, $parsedParameters)) {

            // Set properties with the values from the parsed parameters
            $this->table = $parsedParameters[INLINE_EDIT_TABLE];
            $this->column = $parsedParameters[INLINE_EDIT_COLUMN];
            $this->feType = $parsedParameters[FE_TYPE];
            $this->recordId = $parsedParameters[INLINE_EDIT_RECORD_ID];

            // Error handling missing or invalid parameters
        } else {
            $error = $this->checkGivenParameters($parsedParameters);
            throw new \UserReportException ($error[INLINE_EDIT_ERROR_MESSAGE], $error[INLINE_EDIT_ERROR_CODE]);
        }

        // Get value given the recordId, tableName and columnName
        $result = $this->database->sql("SELECT $this->column FROM $this->table WHERE `id` = ?", ROW_EXPECT_0_1, [$this->recordId]);

        // Check if record for given table exists
        if (empty($result)) {
            throw new \UserReportException ('Record with id "' . $parsedParameters[INLINE_EDIT_RECORD_ID]
                . '" for table "' . $this->table . '" does not exist.', ERROR_INLINE_EDIT_RECORD_NONEXISTENT);
        }

        // Set value to result of query given the columnName
        $this->value = $result[$this->column];
    }

    /**
     * Create the HTML of the inline edit container and label.
     * Container stores parameters in data-sip attribute.
     *
     * @return string HTML of the inline edit container and label.
     * @throws CodeException
     * @throws UserReportException
     * @throws DbException
     *
     * @throws UserFormException
     */
    public function render(): string {
        // &table has to be named 'table' because of QuickFormQuery.php line 517
        // Save values in SIP_STORE, so they later can be used in different instance
        $dataSip = $this->link->renderLink('p:&r=' . $this->recordId
            . '&table=' . $this->table
            . '&column=' . $this->column
            . '&feType=' . $this->feType
            . '&value=' . $this->value
            . '&pageId=' . ($this->pageId ?? (strval($this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3))))
            . '&formName=' . ($this->formName ?? 'noForm')
            . '&formId=' . strval($this->formId)
            . '|s|r:8'
        );

        // Wrap the value with the HTML tags for the label and the inline edit container, sip is stored in the 'data-sip' attribute of the container
        $html = Support::wrapTag('<div class="qfq-inline-edit-label">', $this->value);
        $html = Support::wrapTag('<div class="qfq-inline-edit" data-sip="' . $dataSip . '">', $html);

        return $html;
    }

    /**
     * Validates the given parameters for special column name "_edit".
     * This method checks the provided parameters against the expected format for two different modes: "reference" and "direct".
     * In the "reference" mode, the expected parameters are "fe" and "r". In the "direct" mode, the expected parameters are "column", "type", and "r".
     * If the parameters are missing or invalid, an error message and error code are returned.
     *
     * @param array $parsedParameters An associative array containing the parsed parameters.
     *
     * @return array An associative array containing the error message and error code.
     */
    public function checkGivenParameters(array $parsedParameters): array {
        $missingParameters = array();
        if (isset($parsedParameters[INLINE_EDIT_FORM])) {
            // Version reference mode: 'form=ipa&fe=text1&r=2'
            if (!array_key_exists(INLINE_EDIT_FORM_ELEMENT, $parsedParameters)) {
                $missingParameters[] = INLINE_EDIT_FORM_ELEMENT;
            }
            if (!array_key_exists(INLINE_EDIT_RECORD_ID, $parsedParameters)) {
                $missingParameters[] = INLINE_EDIT_RECORD_ID;
            }
            $errorMessage = 'Missing parameters for special column name "_edit" : ' . implode(', ', $missingParameters);
            $errorCode = ERROR_INLINE_EDIT_MISSING_REQUIRED_PARAMETER;
        } elseif (isset($parsedParameters[INLINE_EDIT_TABLE])) {
            // Version direct mode: 'table=Dummy&column=text1&type=text&r=2'
            if (!array_key_exists(INLINE_EDIT_COLUMN, $parsedParameters)) {
                $missingParameters[] = INLINE_EDIT_COLUMN;
            }
            if (!array_key_exists(FE_TYPE, $parsedParameters)) {
                $missingParameters[] = FE_TYPE;
            }
            if (!array_key_exists(INLINE_EDIT_RECORD_ID, $parsedParameters)) {
                $missingParameters[] = INLINE_EDIT_RECORD_ID;
            }
            $errorMessage = 'Missing parameters for special column name "_edit": ' . implode(', ', $missingParameters);
            $errorCode = ERROR_INLINE_EDIT_MISSING_REQUIRED_PARAMETER;
        } else {
            $errorMessage = 'Invalid parameters for special column name "_edit" provided.';
            $errorCode = ERROR_INLINE_EDIT_INVALID_PARAMETER_GIVEN;
        }

        return array(INLINE_EDIT_ERROR_MESSAGE => $errorMessage, INLINE_EDIT_ERROR_CODE => $errorCode);
    }

    // Getter functions

    /**
     * Get the form element type.
     *
     * @return string|null The frontend type of the form element, or null if not set.
     */
    public function getFeType(): ?string {
        return $this->feType;
    }

    /**
     * Get the record ID associated with the form element.
     *
     * @return string|null The record ID of the form element, or null if not set.
     */
    public function getRecordId(): ?string {
        return $this->recordId;
    }

    /**
     * Get the form name of the form element.
     *
     * @return string|null The form name of the form element, or null if not set.
     */
    public function getFormName(): ?string {
        return $this->formName;
    }

    /**
     * Get the column name.
     *
     * @return string|null The column name.
     */
    public function getColumn(): ?string {
        return $this->column;
    }

    /**
     * Get the value.
     *
     * @return string|null The value of the Inline Edit Input Element.
     */
    public function getValue(): ?string {
        return $this->value;
    }

    /**
     * Get the table name.
     *
     * @return string|null The table name of the Inline Edit Input Element.
     */
    public function getTable(): ?string {
        return $this->table;
    }

    /**
     * Set the form element type .
     *
     * @param string|null $feType The frontend type of the form element.
     *                           Set to null to unset the value.
     *
     * @return void
     */
    public function setFeType(?string $feType): void {
        $this->feType = $feType;
    }

    /**
     * Set the record ID associated with the form element.
     *
     * @param string|null $recordId The record ID of the form element.
     *                             Set to null to unset the value.
     *
     * @return void
     */
    public function setRecordId(?string $recordId): void {
        $this->recordId = $recordId;
    }

    /**
     * Set the form name of the form element.
     *
     * @param string|null $formId The form name of the form element.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setFormId(?string $formId): void {
        $this->formId = $formId;
    }

    /**
     * Set the table name of the form element.
     *
     * @param string|null $table The table name of the form element.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setTable(?string $table): void {
        $this->table = $table;
    }

    /**
     * Set the column name of the form element.
     *
     * @param string|null $column The column name of the form element.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setColumn(?string $column): void {
        $this->column = $column;
    }

    /**
     * Set the value of the form element.
     *
     * @param string|null $value The value of the form element.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setValue(?string $value): void {
        $this->value = $value;
    }

    /**
     * Set the form name of the form element.
     *
     * @param string|null $formName The form name of the form element.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setFormName(?string $formName): void {
        $this->formName = $formName;
    }

    /**
     * Set the pageId of the form element.
     *
     * @param string|null $pageId The pageId of the typo3 page.
     *                            Set to null to unset the value.
     *
     * @return void
     */
    public function setPageId(?string $pageId): void {
        $this->pageId = $pageId;
    }

    /**
     * Set the Link Object of the form element.
     *
     * @param string|null $link The Link Class.
     *                          Set to null to unset the value.
     *
     * @return void
     */
    public function setLink(?Link $link): void {
        $this->link = $link;
    }

}
