<?php
/**
 * @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
 */

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

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Session\Session;
use Joomla\Registry\Registry;
use Joomla\CMS\Language\Text;

require_once dirname(__DIR__) . '/helpers/field.php';

class JFormFieldNRChainedFields extends NRFormField
{
	/**
	 * All CSV choices.
	 * 
	 * @var  array
	 */
	protected $choices = [];

	/**
	 * The separator used in the CSV file.
	 * 
	 * @var  string
	 */
	protected $separator = ',';

	/**
	 * The data source contents.
	 * 
	 * @var  string
	 */
	protected $dataset = '';
	
	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 */
	protected function getInput()
	{
		$layout = new FileLayout('chainedfields', JPATH_PLUGINS . '/system/nrframework/layouts');

		$data_source = $this->get('data_source', 'custom');

		$data = '';
		switch ($data_source)
		{
			case 'custom':
				$data = $this->get('data_source_custom', '');
				break;

			case 'csv_file':
				$data = $this->get('data_source_csv', '');
				break;
		}

		$this->dataset = \NRFramework\Helpers\ChainedFields::normalizeCSVFileSource($data_source, $data);

		$this->separator = $this->get('separator', ',');

		$data = [
			'csv' => $this->dataset,
			'data_source' => $data_source,
			'value' => $this->getValue(),
			'data' => $this->getData(),
			'ajaxUrl' => $this->get('ajaxUrl')
		];

		return $layout->render($data);
	}

	/**
	 * Returns the field value.
	 * 
	 * @return  mixed
	 */
	private function getValue()
	{
		if (!$this->value)
		{
			return null;
		}

		if (!$this->choices = \NRFramework\Helpers\ChainedFields::loadCSV($this->dataset, $this->get('data_source', 'custom'), $this->separator, $this->id . '_', $this->name))
		{
			return null;
		}

		$choices = $this->choices['inputs'];

		// Ensure the value is in correct format when used as plain field or in subform
		$this->value = array_values((array) $this->value);

		foreach ($this->choices['inputs'] as $key => $input)
		{
			$choices[$key]['choices'] = $this->getSelectedChoices($key, $this->value, $input);
		}

		return $choices;
	}

	/**
	 * Finds the choices of the select.
	 * 
	 * @param   string  $key
	 * @param   array   $value
	 * @param   array   $input
	 * 
	 * @return  mixed
	 */
	private function getSelectedChoices($key, $value, $input)
	{
		$choices = $this->getSelectChoices($value, $input['id']);

		if (!is_array($choices))
		{
			return null;
		}

		// set selected options based on value
		foreach ($choices as $_key => &$choice)
		{
			if (!isset($value[$key]) || $choice['value'] !== $value[$key])
			{
				continue;
			}

			$choice['isSelected'] = true;
		}

		return $choices;
	}

    /**
     * Handles the AJAX request.
	 * 
	 * Runs when select a value from a select field.
     *
     * @param   array    $options
     *
     * @return  array
     */
    public function onAjax($options)
    {
		if (!Session::checkToken('request'))
        {
            echo json_encode([
				'error' => true,
				'response' => Text::_('JINVALID_TOKEN')
			]);
			jexit();
        }
		
		$options = new Registry($options);
	
		if (!$select_id = $options->get('select_id'))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		if (!$value = $options->get('value'))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		if (!$value = json_decode($options->get('value'), true))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		if (!$data_source = $options->get('data_source'))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		if (!$csv = $options->get('csv'))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		$csv = json_decode($csv, true);

		// Verify that the CSV file exists and is a CSV file
		if ($data_source === 'csv_file' && !empty($csv))
		{
			$mime = \NRFramework\Mimes::detectFileType($csv);
			
			if (!is_string($csv) || !file_exists($csv) || $mime !== 'text/csv')
			{
				echo json_encode([
					'error' => true,
					'response' => Text::_('NR_CSV_FILE_NOT_FOUND_OR_INVALID')
				]);
				jexit();
			}
		}
		
		// get field ID from select ID
		$id = preg_replace('/_[0-9]+$/', '', $select_id);

		if (!$id || !$select_id || !$value || !$csv)
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		$this->id = $id;

		// get all CSV data
		if (!$this->choices = \NRFramework\Helpers\ChainedFields::loadCSV($csv, $data_source, $this->separator, $this->id . '_', $this->name))
		{
            echo json_encode([
				'error' => true,
				'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
			]);
			jexit();
		}

		// find next select options
		$this->findNextOptions($id, $select_id, $value);
	}

	/**
	 * Finds the options of the next select.
	 * 
	 * @param   string  $id
	 * @param   string  $select_id
	 * @param   array   $value
	 * 
	 * @return  void
	 */
	private function findNextOptions($id, $select_id, $value)
	{
		// find next ID
		$next_select_id = $this->getNextSelectID($id, $select_id);

		// get next choices
		$choices = $next_select_id ? $this->getSelectChoices($value, $next_select_id) : [];

		echo json_encode([
			'error' => false,
			'response' => $choices
		]);
		jexit();
	}

	/**
	 * Returns the select choices.
	 * 
	 * @param   array    $field_value
	 * @param   string   $select_id
	 * @param   integer  $depth
	 * @param   array    $choices
	 * @param   array    $full_field_value
	 * 
	 * @return  array
	 */
	private function getSelectChoices($field_value = null, $select_id = null, $depth = null, $choices = null, $full_field_value = null)
	{
		$full_field_value = $full_field_value !== null ? $full_field_value : $field_value;
		$value            = array_shift($field_value);
		$index            = $select_id ? $this->getSelectID($select_id) : 1;
		$depth            = $depth ? $depth : 1;
		$choices          = $choices === null ? $this->choices['choices'] : (empty($choices) ? [] : $choices);
		$select_choices   = [];

		if ($depth == $index)
		{
			$select_choices = $choices;
		}
		else
		{
			foreach ($choices as $choice)
			{
				if ($choice['value'] !== $value)
				{
					continue;
				}

				$select_choices = $this->getSelectChoices($field_value, $select_id, $depth + 1, !empty($choice['choices']) ? $choice['choices'] : [], $full_field_value);
				break;
			}
		}

		if (empty($select_choices) && $this->getPreviousSelectValue($select_id, $full_field_value))
		{
			$select_choices = [
				[
					'text'       => 'No options',
					'value'      => '',
					'isSelected' => true
				]
			];
		}

		return $select_choices;
	}

	/**
	 * Returns the previous select value
	 * 
	 * @param   string  $select_id
	 * @param   string  $full_field_value
	 * 
	 * @return  string
	 */
	public function getPreviousSelectValue($select_id, $full_field_value)
	{
		$explode = explode('_', $select_id);

		$input_id = $explode[count($explode) - 1];
		$field_id = rtrim($select_id, '_' . $input_id);

		$prev_input_id = sprintf('%s.%s', $field_id, $input_id - 1);
		$prev_input_value = isset($full_field_value[$prev_input_id]) ? $full_field_value[$prev_input_id] : null;

		return $prev_input_value;
	}

	/**
	 * Finds the next select ID
	 * 
	 * @param   string  $base_id
	 * @param   string  $select_id
	 * 
	 * @return  mixed
	 */
	public function getNextSelectID($base_id, $select_id)
	{
		$id = $this->getSelectID($select_id);
		$next_id = $id + 1;

		if ($next_id % 10 == 0)
		{
			$next_id++;
		}

		$next_select_id = sprintf('%s_%d', $base_id, $next_id);

		foreach ($this->choices['inputs'] as $input)
		{
			if ($input['id'] != $next_select_id)
			{
				continue;
			}

			return $next_select_id;
		}

		return false;
	}

	/**
	 * Gets the ID (index) of the select by its ID.
	 * 
	 * @param   string  $select_id
	 * 
	 * @return  int
	 */
	public function getSelectID($select_id)
	{
		$explode = explode('_', $select_id);

		return (int) array_pop($explode);
	}

	/**
	 * Fetches all CSV data.
	 * 
	 * @return  array
	 */
	private function getData()
	{
		if (!$this->dataset)
		{
			return [];
		}

		if (!$choices = \NRFramework\Helpers\ChainedFields::loadCSV($this->dataset, $this->get('data_source', 'csv_file'), $this->separator, $this->id . '_', $this->name))
		{
			return [];
		}

		return $choices;
	}
}