<?php

namespace traits;

use Exception;

trait module_config_variable
{
    public string $module_name; // The name of the module
    public string $config_variable; // The name of the config variable in the database (e.g. layout_id)
    public string $config_variable_type; // The type of the config variable (e.g. int)
    public bool $config_variable_required; // Whether the config variable is required or not
    public array|null $allowed_values; // An array of allowed values for the config variable. If this is null, any value is allowed
    public string $config_variable_description; // A description of the config variable
    public string $config_variable_example; // An example of the config variable
    public bool $config_variable_is_secret; // Whether the config variable is a secret or not

    /**
     * Set up the config variable
     * @param string $module_name
     * @param string $config_variable
     * @param string $config_variable_type
     * @param bool $config_variable_required
     * @param array|null $allowed_values
     * @param string $config_variable_description
     * @param string $config_variable_example
     * @param bool $config_variable_is_secret
     * @param mixed $default_value
     * @return void
     * @throws Exception
     */
    function setupConfigVariable(
        string     $module_name,
        string     $config_variable,
        string     $config_variable_type,
        bool       $config_variable_required,
        array|null $allowed_values,
        string     $config_variable_description,
        string     $config_variable_example,
        bool       $config_variable_is_secret = false,
        mixed      $default_value = null
    ): void
    {
        $this->module_name = $module_name;
        $this->config_variable = $config_variable;
        $this->config_variable_type = $config_variable_type;
        $this->config_variable_required = $config_variable_required;
        $this->allowed_values = $allowed_values;
        $this->config_variable_description = $config_variable_description;
        $this->config_variable_example = $config_variable_example;
        $this->config_variable_is_secret = $config_variable_is_secret;

        // Check if the config variable is set in the database
        if (!self::isVariableSet($module_name, $config_variable)) {
            // If the config variable is required, throw an exception
            if ($config_variable_required && $default_value === null) {
                throw new Exception('Config variable not set: ' . $config_variable);
            } else {
                // If the config variable is not required, set the default value
                self::insertVariableValue($module_name, $config_variable, $default_value);
            }
        }
    }

    /**
     * Check if the config variable is set in the database
     * @param string $module
     * @param string $variable
     * @return bool
     */
    static function isVariableSet(string $module, string $variable): bool
    {
        global $db;
        $sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'";
        $result = $db->query($sql);
        return $result->num_rows > 0;
    }

    /**
     * Insert the value of the config variable in the database
     * @param string $module
     * @param string $variable
     * @param mixed $value
     */
    function insertVariableValue(string $module, string $variable, mixed $value): void
    {
        global $db;
        $sql = "INSERT INTO module_config (module, variable, value, type) VALUES ('$module', '$variable', '$value', '" . self::getVariableType() . "')";
        $db->query($sql);
    }

    /**
     * Get the variable type
     * @return string
     */
    function getVariableType(): string
    {
        return $this->config_variable_type;
    }

    /**
     * Is boolean variable true?
     * @return bool
     * @throws Exception
     */
    function isTrue(): bool
    {
        // Make sure the variable is a boolean
        if ($this->config_variable_type !== 'bool') {
            throw new Exception('Config variable is not a boolean: ' . $this->config_variable . ' (' . $this->config_variable_type . ')');
        }
        return $this->getVariableValue() === 'true';
    }

    /**
     * Get the value of the config variable from the database
     * @return mixed
     */
    function getVariableValue(): mixed
    {
        global $db;
        $sql = "SELECT value FROM module_config WHERE module = '$this->module_name' AND variable = '$this->config_variable'";
        $result = $db->query($sql);
        // return the value of the config variable
        return $result->fetch_assoc()['value'];
    }

    public function __toString(): string
    {
        // Return the config variable value as a string
        return $this->getVariableValue();
    }

    /**
     * Set the value of the config variable in the database
     * @param mixed $value
     * @throws Exception
     */
    function setVariableValue(mixed $value): void
    {
        // Check if the variable value is valid
        if (!$this->validateVariableValue($value)) {
            throw new Exception('Invalid value for config variable: ' . $this->config_variable . ' (' . $value . ')');
        }
        // If the type is a boolean, convert the value to "true" or "false"
        if ($this->config_variable_type === 'bool') {
            $value = $value ? 'true' : 'false';
        }
        // Update the value of the config variable in the database, and insert it if it does not exist
        if (self::isVariableSet($this->module_name, $this->config_variable)) {
            self::updateVariableValue($this->module_name, $this->config_variable, $value);
        } else {
            self::insertVariableValue($this->module_name, $this->config_variable, $value);
        }
    }

    /**
     * Validate the value of the config variable
     * @param mixed $value
     * @return bool
     */
    function validateVariableValue(mixed $value): bool
    {
        // Check if the value is empty and the variable is required
        if ($this->config_variable_required && empty($value)) {
            // If the value is empty and the type is not a boolean, return false
            if ($this->config_variable_type != 'bool') {
                return false;
            }
        }

        // Check if the value is in the list of allowed values
        if ($this->allowed_values && !in_array($value, $this->allowed_values)) {
            return false;
        }

        // Check if the value is of the correct type
        switch ($this->config_variable_type) {
            case 'int':
                if (!is_numeric($value)) {
                    return false;
                }
                break;
            case 'string':
                if (!is_string($value)) {
                    return false;
                }
                break;
            case 'bool':
                // If the value is a string, convert it to "true" or "false"
                if (is_string($value)) {
                    $value = self::inputToBool($value);
                }
                if (!is_bool($value)) {
                    return false;
                }
                break;
            default:
                return false;
        }
        return true;
    }

    /**
     * Convert a string to a boolean
     * @param string $value
     * @return bool
     */
    static function inputToBool(string $value): bool
    {
        // If the value is true, 1, or "true", return true
        if ($value === 'true' || $value === '1') {
            return true;
        }
        // If the value is false, 0, or "false", return false
        return false;
    }

    /**
     * Update the value of the config variable in the database
     * @param string $module
     * @param string $variable
     * @param mixed $value
     */
    static function updateVariableValue(string $module, string $variable, mixed $value): void
    {
        global $db;
        $sql = "UPDATE module_config SET value = '$value' WHERE module = '$module' AND variable = '$variable'";
        $db->query($sql);
    }

    /**
     * Get the config variable as a JSON string
     * @return string
     */
    function asJson(): string
    {
        return json_encode($this->asArray());
    }

    /**
     * Get the config variable as an array
     * @return array
     */
    function asArray(): array
    {
        return [
            'module' => $this->module_name,
            'variable' => $this->config_variable,
            'type' => $this->config_variable_type,
            'required' => $this->config_variable_required,
            'allowed_values' => $this->allowed_values,
            'description' => $this->config_variable_description,
            'example' => $this->config_variable_example,
            'value' => $this->getVariableValue(),
        ];
    }

    /**
     * Get the variable name
     * @return string
     */
    function getVariableName(): string
    {
        return $this->config_variable;
    }

    /**
     * Get the variable description
     * @return string
     */
    function getVariableDescription(): string
    {
        return $this->config_variable_description;
    }

    /**
     * Get the variable example
     * @return string
     */
    function getVariableExample(): string
    {
        return $this->config_variable_example;
    }

    /**
     * Get the variable is secret
     * @return bool
     */
    function getVariableIsSecret(): bool
    {
        return $this->config_variable_is_secret;
    }

    /**
     * Get the variable required
     * @return bool
     */
    function getVariableRequired(): bool
    {
        return $this->config_variable_required;
    }
}