<?php

/**
 * @package         Convert Forms
 * @version         5.1.2 Free
 * 
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/

namespace ConvertForms;

defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Language\Text;

/**
 *  Field Choice Class used by dropdown, checkbox and radio fields
 */
class FieldChoice extends \ConvertForms\Field
{
	/**
	 *  Remove common fields from the form rendering
	 *
	 *  @var  mixed
	 */
	protected $excludeFields = [
        'browserautocomplete',
    ];

	/**
	 *  Set field object
	 *
	 *  @param  mixed  $field  Object or Array Field options
	 */
	public function setField($field)
	{
        parent::setField($field);

        // Get input options
        $this->field->choices = $this->getChoices();

        // Use the selected choice as the field value if we don't have a default value set.
        if ($this->field->value == '')
        {
            foreach ($this->field->choices as $choice)
            {
                if ($choice['selected'])
                {
                    $this->field->value = $choice['value'];
                    break;
                }
            }
        }

        if ($this->multiple && !is_array($this->field->value))
        {
            $this->field->value = explode(',', $this->field->value);
        }

		return $this;
	}

    /**
     * Get an array of all field options
     *
     * @return array
     */
    public function getOptions()
    {
        $options = [];

        foreach ($this->field->get('choices.choices', []) as $option)
        {
            $option = (array) $option;

            if (!isset($option['label']) || $option['label'] == '')
            {
                continue;
            }

            // Prepare all options, not only the Label (Remember, we can use Smart Tags in any option, right?) with Smart Tags.
            $option = \NRFramework\SmartTags::getInstance()->replace($option);

            $label = trim($option['label']);
            $value = $option['value'] == '' ? $label : $option['value'];
            $calcValue = isset($option['calc-value']) && $option['calc-value'] !== '' ? $option['calc-value'] : $value;
            $isSelected = isset($option['default']) && $option['default'] ? true : false;

            $options[] = [
                'label'      => $label,
                'value'      => $value,
                'calc-value' => $calcValue,
                'selected'   => $isSelected
            ];
        }

        return $options;
    }

	/**
	 *  Set the field choices
	 *
	 *  Return Array sample
	 *
	 *  $choices = array(
     *  	'label'      => 'Color',
     *   	'value'      => 'color,
     *   	'calc-value' => '150r,
     *  	'selected'   => true,
     *   	'disabled'   => false
	 *  )
	 *
	 *  @return  array  The field choices array
	 */
	protected function getChoices()
	{
        $field = $this->field;

		if (!isset($field->choices) || !isset($field->choices['choices']))
        {
            return;
        }

        $choices = array();
        $hasPlaceholder = (isset($field->placeholder) && !empty($field->placeholder));

        // Create a new array of valid only choices
        // @todo Use $this->getOptions() to get the field options
        foreach ($field->choices['choices'] as $key => $choiceValue)
        {
            if (!isset($choiceValue['label']) || $choiceValue['label'] == '')
            {
                continue;
            }

            $label = trim($choiceValue['label']);
            
            // Temporary workaround: strip_tags shouldn't be here in the first place
            // Only strip tags if choice value is empty and we have both opening and closing HTML tags
            $cleanLabel = $choiceValue['value'] == '' && preg_match('/<[^>]*>/', $label) ? strip_tags($label) : $label;
            $value = $choiceValue['value'] == '' ? $cleanLabel : $choiceValue['value'];

            $choices[] = array(
                'label'      => $label,
                'value'      => $value,
                'calc-value' => (isset($choiceValue['calc-value']) && $choiceValue['calc-value'] != '' ? $choiceValue['calc-value'] : $value),
                'selected'   => (isset($choiceValue['default']) && $choiceValue['default'] && !$hasPlaceholder) ? true : false
            );
        }

        // If we have a placeholder available, add it to dropdown choices.
        if ($hasPlaceholder)
        {
            array_unshift($choices, array(
                'label'    => trim($field->placeholder),
                'value'    => '',
                'selected' => true,
                'disabled' => $field->required == '1' ? true : false
            ));
        }

        return $choices;
	}

    /**
     * In choice-based fields, it makes more sense to display the choice's label instead of the choice's value when the field is used for presentation purposes like in the
     *  
     *   1. Thank you Message
     *   2. {all_fields} Smart Tag
     *   3. Form editing page in the back-end
     *   4. PDF Form Submission addon. 
     * 
     *  While we keep using the choice's value in functions like
     *   1. Calculations
     *   2. Conditional fields
     *   3. JSON API.
     *   4. {field.FIELDNAME}
     *
     * @param  string $value The raw value as stored in the database / submitted by the user
     * 
     * @return string
     */
	public function prepareValueHTML($value)
	{
        if (is_array($value))
        {
            foreach ($value as &$value_)
            {
                $value_ = $this->findChoiceLabelByValue($value_);
            }
        } else 
        {
            $value = $this->findChoiceLabelByValue($value);
        }

        return parent::prepareValueHTML($value);
	}

    /**
	 * Event fired during form saving in the backend to help us validate user options.
	 *
	 * @param  object	$model			The Form Model
	 * @param  array	$form_data		The form data to be saved
	 * @param  array	$field_options	The field data
	 *
	 * @return bool
	 */
	public function onBeforeFormSave($model, $form_data, &$field_options)
	{
        // Call parent method as its needed
        parent::onBeforeFormSave($model, $form_data, $field_options);

        $valid = true;

        $choices = isset($field_options['choices']['choices']) ? $field_options['choices']['choices'] : [];

        if (!is_array($choices) || empty($choices))
        {
            $valid = false;
        }

        // Ensure we have at least 1 choice
        $totalValid = array_filter($choices, function($choice)
        {
            return isset($choice['label']) && $choice['label'] !== '';
        });

        if (count($totalValid) == 0)
        {
            $valid = false;
        }
        
        if (!$valid)
        {
            $model->setError(Text::sprintf('COM_CONVERTFORMS_NO_CHOICES', $field_options['label']));
        }

		return $valid;
	}

    /**
     * Attempt to assosiate a choice value with a choice label. As a fallback the raw value will be returned.
     * 
     * The attempt may fail for one of the reasons below:
     * 
     *   1. A choice value is renamed.
     *   2. A choice is removed.
     *   3. Multiple choices has the same value (which obviously is a mistake by the user).
     * 
     * The most reliable way to always know the choice's label, is to implement this task https://smilemotive.teamwork.com/#/tasks/30244858
     * and start storing in the database a choice's unique ID instead of the choice's value.
     *
     * @param  mixed $value
     * 
     * @return string
     */
    private function findChoiceLabelByValue($value)
    {
        // In multiple choice fields, the value can't be empty.
        if ($value == '')
        {
            return $value;
        }

        if ($options = $this->getOptions())
        {
            foreach ($options as $option)
            {
                // We might lowercase both values?
                if ($option['value'] == $value)
                {
                    return $option['label'];
                }
            }
        }

        // If we can't assosiacte the given value with a label, return the raw value as a fallback.
        return $value;
    }
}