<?php

use EventEspresso\core\services\loaders\LoaderFactory;
use EventEspresso\core\services\request\RequestInterface;

/**
 * EE_Question_Form_Input class
 *    a conglomerate type object that combines an EE_Question object with an EE_Answer object
 *    as well as some of its own class properties to facilitate use within the EEH_Form_Fields.helper class
 *    access to the EE_Question and EE_Answer objects properties are done indirectly via a super getter and setter
 *
 * @package        Event Espresso
 * @subpackage     includes/classes/EE_Question_Form_Input.class.php
 * @author         Brent Christensen
 */
class EE_Question_Form_Input
{
    protected RequestInterface $request;

    protected array $form_data;

    private ?EE_Question $_QST = null;


    private ?EE_Answer $_ANS = null;

    /**
     * $_QST_meta
     * @var array
     */
    private array $_QST_meta = [];

    /**
     * $QST_input_name
     * @var string
     */
    private string $QST_input_name = '';

    /**
     * $QST_input_id
     * @var string
     */
    private string $QST_input_id = '';

    /**
     * $QST_input_class
     * @var string
     */
    private string $QST_input_class = '';

    private bool $QST_disabled = false;


    /**
     * @param EE_Question|null $QST EE_Question object
     * @param EE_Answer|null   $ANS EE_Answer object
     * @param array            $q_meta
     * @throws EE_Error
     * @throws ReflectionException
     */
    public function __construct(EE_Question $QST = null, EE_Answer $ANS = null, array $q_meta = [])
    {
        $this->request   = LoaderFactory::getLoader()->getShared(RequestInterface::class);
        $this->form_data = $this->request->requestParams();
        if (empty($QST) || empty($ANS)) {
            EE_Error::add_error(
                esc_html__(
                    'An error occurred. A valid EE_Question or EE_Answer object was not received.',
                    'event_espresso'
                ),
                __FILE__,
                __FUNCTION__,
                __LINE__
            );
            return null;
        }
        $this->_QST = $QST;
        $this->_ANS = $ANS;
        $this->set_question_form_input_meta($q_meta);
        $this->set_question_form_input_init();
    }


    /**
     * sets meta data for the question form input
     *
     * @param array $q_meta
     * @return void
     */
    public function set_question_form_input_meta(array $q_meta = [])
    {
        $default_q_meta  = [
            'att_nmbr'       => 1,
            'ticket_id'      => '',
            'date'           => '',
            'time'           => '',
            'input_name'     => '',
            'input_id'       => '',
            'input_class'    => '',
            'label_class'    => '',
            'input_prefix'   => 'qstn',
            'append_qstn_id' => true,
            'htmlentities'   => true,
            'allow_null'     => false,
        ];
        $this->_QST_meta = array_merge($default_q_meta, $q_meta);
    }


    /**
     * @return void
     * @throws EE_Error
     * @throws ReflectionException
     */
    public function set_question_form_input_init()
    {
        $qstn_id = $this->_QST->system_ID() ? $this->_QST->system_ID() : $this->_QST->ID();
        $this->_set_input_name($qstn_id);
        $this->_set_input_id($qstn_id);
        $this->_set_input_class();
        $this->set_question_form_input_answer($qstn_id);
    }


    /**
     * @param $qstn_id
     * @return void
     * @throws EE_Error
     * @throws ReflectionException
     */
    private function _set_input_name($qstn_id)
    {
        if (! empty($qstn_id)) {
            $ANS_ID  = $this->get('ANS_ID');
            $qstn_id = ! empty($ANS_ID) ? '[' . $qstn_id . '][' . $ANS_ID . ']' : '[' . $qstn_id . ']';
        }
        $this->QST_input_name = $this->_QST_meta['append_qstn_id'] && ! empty($qstn_id)
            ? $this->_QST_meta['input_prefix'] . $this->_QST_meta['input_name'] . $qstn_id
            : $this->_QST_meta['input_prefix'] . $this->_QST_meta['input_name'];
    }


    /**
     * get property values for question form input
     *
     * @param string $property
     * @return mixed
     * @throws EE_Error
     * @throws ReflectionException
     */
    public function get(string $property = '')
    {
        if (empty($property)) {
            return null;
        }
        if (EEM_Question::instance()->has_field($property)) {
            return $this->_QST->get($property);
        }
        if (EEM_Answer::instance()->has_field($property)) {
            return $this->_ANS->get($property);
        }
        if (property_exists($this, $property)) {
            return $this->{$property};
        }
        return null;
    }


    /**
     * set property values for question form input
     *
     * @param string $property
     * @param mixed  $value
     * @return void
     * @throws EE_Error
     * @throws ReflectionException
     */
    public function set(string $property = '', $value = null)
    {
        if (empty($property)) {
            return;
        }
        if (EEM_Question::instance()->has_field($property)) {
            $this->_QST->set($property, $value);
        }
        if (EEM_Answer::instance()->has_field($property)) {
            $this->_ANS->set($property, $value);
        }
        if (property_exists($this, $property)) {
            $this->{$property} = $value;
        }
    }


    /**
     * @param $qstn_id
     * @return void
     * @throws EE_Error
     * @throws ReflectionException
     */
    private function _set_input_id($qstn_id)
    {
        $input_id           = isset($this->_QST_meta['input_id']) && ! empty($this->_QST_meta['input_id'])
            ? $this->_QST_meta['input_id']
            : sanitize_key(strip_tags($this->_QST->get('QST_display_text')));
        $this->QST_input_id = $this->_QST_meta['append_qstn_id'] && ! empty($qstn_id)
            ? $input_id . '-' . $qstn_id
            : $input_id;
    }


    /**
     * @return void
     */
    private function _set_input_class()
    {
        $this->QST_input_class = $this->_QST_meta['input_class'] ?? '';
    }


    /**
     * @param int|string $qstn_id
     * @return void
     * @throws EE_Error
     * @throws ReflectionException
     */
    public function set_question_form_input_answer($qstn_id)
    {
        // check for answer in $this->form_data in case we are reprocessing a form after an error
        if (
            isset($this->_QST_meta['EVT_ID'])
            && isset($this->_QST_meta['att_nmbr'])
            && isset($this->_QST_meta['date'])
            && isset($this->_QST_meta['time'])
            && isset($this->_QST_meta['price_id'])
        ) {
            $EVT_ID   = $this->_QST_meta['EVT_ID'];
            $att_nmbr = $this->_QST_meta['att_nmbr'];
            $date     = $this->_QST_meta['date'];
            $time     = $this->_QST_meta['time'];
            $price_id = $this->_QST_meta['price_id'];
            if (isset($this->form_data['qstn'][ $EVT_ID ][ $att_nmbr ][ $date ][ $time ][ $price_id ][ $qstn_id ])) {
                $answer = $this->form_data['qstn'][ $EVT_ID ][ $att_nmbr ][ $date ][ $time ][ $price_id ][ $qstn_id ];
                $this->_ANS->set('ANS_value', $answer);
            }
        }
    }


    /**
     * @param EE_Base_class $object $object
     * @param array         $input_types
     * @return array
     * @throws EE_Error
     * @throws ReflectionException
     */
    public static function generate_question_form_inputs_for_object(
        EE_Base_class $object,
        array $input_types = []
    ): array {
        $inputs = [];
        $fields = $object->get_model()->field_settings();
        foreach ($fields as $field_ID => $field) {
            if ($field instanceof EE_Model_Field_Base) {
                if (isset($input_types[ $field_ID ])) {
                    // label to display
                    $label = $input_types[ $field_ID ]['label'] ?? $field->get_nicename();
                    // get saved value for field
                    $value = $object->get($field_ID);
                    // if no saved value, then use default
                    $value = $value ?? $field->get_default_value();
                    // determine question type
                    $type = $input_types[ $field_ID ]['type'] ?? 'TEXT';
                    // input name
                    $input_name = isset($input_types[ $field_ID ]['input_name'])
                        ? $input_types[ $field_ID ]['input_name'] . '[' . $field_ID . ']'
                        : $field_ID;
                    // css class for input
                    $class = $input_types[ $field_ID ]['class'] ?? '';
                    // css class for label
                    $label_class = $input_types[ $field_ID ]['label_class'] ?? '';
                    // whether to apply htmlentities to answer
                    $htmlentities = $input_types[ $field_ID ]['htmlentities'] ?? true;
                    // whether to apply htmlentities to answer
                    $label_b4 = $input_types[ $field_ID ]['label_b4'] ?? false;
                    // whether to apply htmlentities to answer
                    $use_desc_4_label = $input_types[ $field_ID ]['use_desc_4_label'] ?? false;
                    // whether input is disabled
                    $disabled = isset($input_types[ $field_ID ]['disabled']) && $input_types[ $field_ID ]['disabled'];
                    // use a different label on mobile devices
                    $add_mobile_label = isset($input_types[ $field_ID ]['add_mobile_label'])
                        && $input_types[ $field_ID ]['add_mobile_label'];

                    // create EE_Question_Form_Input object
                    $QFI = new EE_Question_Form_Input(
                        EE_Question::new_instance(
                            [
                                'QST_ID'           => 0,
                                'QST_display_text' => $label,
                                'QST_type'         => $type,
                            ]
                        ),
                        EE_Answer::new_instance(
                            [
                                'ANS_ID'    => 0,
                                'QST_ID'    => 0,
                                'REG_ID'    => 0,
                                'ANS_value' => $value,
                            ]
                        ),
                        [
                            'input_id'         => "$field_ID-{$object->ID()}",
                            'input_name'       => $input_name,
                            'input_class'      => "$field_ID $class",
                            'label_class'      => $label_class,
                            'input_prefix'     => '',
                            'append_qstn_id'   => false,
                            'htmlentities'     => $htmlentities,
                            'label_b4'         => $label_b4,
                            'use_desc_4_label' => $use_desc_4_label,
                            'add_mobile_label' => $add_mobile_label,
                        ]
                    );
                    // does question type have options ?
                    if (
                        in_array($type, ['DROPDOWN', 'RADIO_BTN', 'CHECKBOX'])
                        && isset($input_types[ $field_ID ]['options'])
                    ) {
                        foreach ($input_types[ $field_ID ]['options'] as $option) {
                            $option    = stripslashes_deep($option);
                            $option_id = $option['id'] ?? null;
                            $QSO       = EE_Question_Option::new_instance(
                                [
                                    'QSO_value'   => (string) $option_id,
                                    'QSO_desc'    => $option['text'],
                                    'QSO_deleted' => false,
                                ]
                            );
                            // all QST (and ANS) properties can be accessed indirectly thru QFI
                            $QFI->add_temp_option($QSO);
                        }
                    }
                    // we don't want ppl manually changing primary keys cuz that would just lead to total craziness man
                    if ($disabled || $field_ID == $object->get_model()->primary_key_name()) {
                        $QFI->QST_disabled = true;
                    }
                    $inputs[ $field_ID ] = $QFI;
                }
            }
        }
        return $inputs;
    }


    /**
     * @param EE_Question_Option $QSO EE_Question_Option
     * @return void
     */
    public function add_temp_option(EE_Question_Option $QSO)
    {
        $this->_QST->add_temp_option($QSO);
    }


    /**
     * @param boolean           $notDeletedOptionsOnly            whether to return ALL options, or only the ones which
     *                                                            have not yet been deleted
     * @param string|array|null $selected_value_to_always_include when retrieving options to an ANSWERED question,
     *                                                            we want to usually only show non-deleted options AND
     *                                                            the value that was selected for the answer, whether
     *                                                            it was trashed or not.
     * @return EE_Question_Option[]
     */
    public function options(bool $notDeletedOptionsOnly = true, $selected_value_to_always_include = null): array
    {
        $temp_options = $this->_QST->temp_options();
        return ! empty($temp_options)
            ? $temp_options
            : $this->_QST->options(
                $notDeletedOptionsOnly,
                $selected_value_to_always_include
            );
    }


    /**
     * @param mixed $key
     * @return mixed
     */
    public function get_meta($key = false)
    {
        return $key && isset($this->_QST_meta[ $key ]) ? $this->_QST_meta[ $key ] : false;
    }
}
