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

// No direct access to this file
defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;

require_once __DIR__ . '/ajaxify.php';

/*
 * Creates an AJAX-based dropdown
 * https://select2.org/
 */
class JFormFieldComponentItems extends JFormFieldAjaxify
{
    /**
     * List of allowed query presets
     *
     * @var array
     */
    protected static $queryPresets = null;

    /**
     * Single items table name
     *
     * @var string
     */
    protected $table = 'content';

    /**
     * Primary key column of the single items table
     *
     * @var string
     */
    protected $column_id = 'id';

    /**
     * The title column of the single items table
     *
     * @var string
     */
    protected $column_title = 'title';

    /**
     * The state column of the single items table
     *
     * @var string
     */
    protected $column_state = 'state';

    /**
     * Pass extra where SQL statement
     *
     * @var string
     */
    protected $where;

    /**
     * Pass extra join SQL statement
     *
     * @var string
     */
    protected $join;

    /**
     * Pass extra group SQL statement
     *
     * @var string
     */
    protected $query_group;

    /**
     * The Joomla database object
     *
     * @var object
     */
    protected $db;

    /**
     * Query preset identifier
     */
    protected $preset;

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 */
    public function setup(SimpleXMLElement $element, $value, $group = null)
    {
		if ($return = parent::setup($element, $value, $group))
		{
            $this->init();
        }
        
		return $return;
    }

    public function init()
    {
        if ($this->element['preset'])
        {
            $this->preset = (string) $this->element['preset'];
        }
        else 
        {
            // Backward compatibility: Support preset detection based on the old field XML elements
            $this->preset = self::findPresetByConfig([
                'table'        => $this->table,
                'column_id'    => (string) $this->element['column_id'],
                'column_title' => (string) $this->element['column_title'],
                'column_state' => (string) $this->element['column_state'],
                'where'        => (string) $this->element['where'],
                'join'         => (string) $this->element['join'],
                'group'        => (string) $this->element['group']
            ]);
        }

        // Get query configuration from preset
        $queryConfig = $this->getPresetConfig();

        $this->table        = $queryConfig['table'];
        $this->column_id    = $this->prefix($queryConfig['column_id']);
        $this->column_title = $this->prefix($queryConfig['column_title']);
        $this->column_state = $this->prefix($queryConfig['column_state']);
        $this->where        = $queryConfig['where'] ?? null;
        $this->join         = $queryConfig['join'] ?? null;
        $this->query_group  = $queryConfig['group'] ?? null;

        $this->append_id_to_label = isset($this->element['append_id_to_label'])
            && (string) $this->element['append_id_to_label'] === 'true';

        if (!isset($this->element['placeholder']) && isset($this->element['description']))
        {
            $this->placeholder = (string) $this->element['description'];
        }

        // Initialize database Object
        $this->db = Factory::getContainer()->get(Joomla\Database\DatabaseInterface::class);
    }

    /**
     * Get ComponentItems AJAX endpoint URL
     */
    protected function getAjaxEndpoint()
    {
        $data = [
            'handler' => 'ComponentItems',
            Session::getFormToken()  => 1,
            'preset' => $this->preset,
            'append_id_to_label' => $this->append_id_to_label ? 1 : 0
        ];

        return URI::base() . '?option=com_ajax&format=raw&plugin=nrframework&' . http_build_query($data);
    }

    private function prefix($string)
    {
        if (strpos($string, '.') === false)
        {
            $string = 'i.' . $string;
        }

        return $string;
    }

    protected function getTemplateResult()
    {
        return '<span class="row-text">\' + state.text + \'</span><span style="float:right; opacity:.7">\' + state.id + \'</span>';
    }

    protected function getItemsQuery()
    {
        $db = $this->db;

        $query = $this->getQuery()
            ->order($db->quoteName($this->column_id) . ' DESC');

        if ($this->limit > 0)
        {
            // Joomla uses offset
            $page = $this->page - 1;

            $query->setLimit($this->limit, $page * $this->limit);
        }

        return $query;
    }

    protected function getItems()
    {
        $db = $this->db;

        $db->setQuery($this->getItemsQuery());
        
		$results = $db->loadObjectList();

        if ($this->append_id_to_label && !empty($results))
        {
            foreach ($results as &$item)
            {
                $item->text = $item->text . ' (' . $item->id . ')';
            }
        }

        return $results;
    }

    protected function getItemsTotal()
    {
        $db = $this->db;

        $subQuery = $this->getQuery()
            ->clear('select')
            ->select($this->column_id);

        if ($this->query_group)
        {
            $subQuery = $this->getQuery()
                ->clear('group')
                ->group($this->column_id);
        }
        
        $subQuery = (string) $subQuery;

        $query = $db->getQuery(true)
            ->select('COUNT(*)')
            ->from("($subQuery) AS x");

        $db->setQuery($query);
        return (int) $db->loadResult();
    }

    protected function getQuery()
    {
        $db = $this->db;

        $query = $db->getQuery(true)
            ->select([
                $db->quoteName($this->column_id, 'id'),
                $db->quoteName($this->column_title, 'text'),
                $db->quoteName($this->column_state, 'state')
            ])
            ->from($db->quoteName('#__' . $this->table, 'i'));

        if (!empty($this->search_term))
        {
            $query->where($db->quoteName($this->column_title) . ' LIKE ' . $db->quote('%' . $this->search_term . '%'));
        }

        if ($this->join)
        {
            $query->join('INNER', $this->join);
        }

        if ($this->where)
        {
            $query->where($this->where);
        }

        if ($this->query_group)
        {
            $query->group($this->query_group);
        }

        return $query;
    }

    protected function validateOptions($options)
    {
        $db = $this->db;

        // Cast all options to integers before imploding
        $safeOptions = array_map('intval', $options);

        $query = $this->getQuery()
            ->where($db->quoteName($this->column_id) . ' IN (' . implode(',', $safeOptions) . ')');

        $db->setQuery($query);

        $results = $db->loadAssocList('id', 'text');

        if ($this->append_id_to_label && !empty($results))
        {
            foreach ($results as $id => &$text)
            {
                $text = $text . ' (' . $id . ')';
            }
        }

        return $results;
    }

    /**
     * Factory method to create the appropriate ComponentItems field instance based on query preset
     *
     * @param string $preset Preset identifier (e.g., 'jshopping', 'virtuemart', 'content')
     * @return JFormFieldComponentItems|JFormFieldJShoppingComponentItems|JFormFieldVirtueMartComponentItems
     */
    public static function createInstance($preset = 'content')
    {
        // Map of presets that point to subclasses of ComponentItems
        static $specialHandlers = [
            'jshopping' => [
                'class' => 'JFormFieldJShoppingComponentItems',
                'file' => 'jshoppingcomponentitems.php'
            ],
            'virtuemart' => [
                'class' => 'JFormFieldVirtueMartComponentItems',
                'file' => 'virtuemartcomponentitems.php'
            ]
        ];

        if (isset($specialHandlers[$preset]))
        {
            $handler = $specialHandlers[$preset];
            require_once __DIR__ . '/' . $handler['file'];
            return new $handler['class']();
        }

        // Default to standard ComponentItems
        return new self();
    }

    /**
     * Initializes query presets map
     *
     * @return array
     */
    protected static function getQueryPresetsMap()
    {
        if (self::$queryPresets !== null)
        {
            return self::$queryPresets;
        }

        self::$queryPresets = [
            'content' => [
                'default' => [
                    'table' => 'content',
                    'column_id' => 'id',
                    'column_title' => 'title',
                    'column_state' => 'state'
                ],
                'published' => [
                    'table' => 'content',
                    'column_id' => 'id',
                    'column_title' => 'title',
                    'column_state' => 'state',
                    'where' => 'i.state >= 0'
                ]
            ],

            'tags' => [
                'default' => [
                    'table' => 'tags',
                    'column_id' => 'id',
                    'column_title' => 'title',
                    'column_state' => 'published',
                    'where' => 'i.published = 1 AND i.alias != \'root\' AND i.level > 0'
                ],
                'acf' => [
                    'table' => 'tags',
                    'column_id' => 'id',
                    'column_title' => 'title',
                    'column_state' => 'published',
                    'where' => 'i.published = 1 AND i.alias != \'root\''
                ]
            ],

            'users' => [
                'default' => [
                    'table' => 'users',
                    'column_id' => 'id',
                    'column_title' => 'name',
                    'column_state' => 'block',
                    'where' => 'i.block = 0'
                ],
                'authors' => [
                    'table' => 'users',
                    'column_id' => 'id',
                    'column_title' => 'i.name',
                    'column_state' => 'i.block',
                    'join' => '#__content as c ON c.created_by = i.id',
                    'group' => 'i.id'
                ]
            ],
            'hikashop' => [
                'table' => 'hikashop_product',
                'column_id' => 'product_id',
                'column_title' => 'product_name',
                'column_state' => 'product_published'
            ],

            'k2items' => [
                'table' => 'k2_items',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published',
                'where' => 'i.trash = 0'
            ],

            'rseventspro' => [
                'table' => 'rseventspro_events',
                'column_id' => 'id',
                'column_title' => 'name',
                'column_state' => 'published'
            ],

            'jreviews' => [
                'table' => 'jreviews_content',
                'column_id' => 'c.id',
                'column_title' => 'c.title',
                'column_state' => 'c.state',
                'join' => '#__content as c ON c.id = i.contentid',
                'where' => 'c.state >= 0'
            ],

            'dpcalendar' => [
                'table' => 'dpcalendar_events',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'state'
            ],

            'j2store' => [
                'table' => 'j2store_products',
                'column_id' => 'j2store_product_id',
                'column_title' => 'c.title',
                'column_state' => 'c.state',
                'join' => '#__content as c ON i.product_source_id = c.id',
                'where' => "i.product_source='com_content' AND c.state >= 0"
            ],

            'zoo' => [
                'table' => 'zoo_item',
                'column_id' => 'id',
                'column_title' => 'name',
                'column_state' => 'state'
            ],

            'icagenda' => [
                'table' => 'icagenda_events',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'state'
            ],

            'sppagebuilder' => [
                'table' => 'sppagebuilder',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published',
                'where' => 'i.published >= 0'
            ],
            'jbusinessdirectory' => [
                'default' => [
                    'table' => 'jbusinessdirectory_companies',
                    'column_id' => 'id',
                    'column_title' => 'name',
                    'column_state' => 'state'
                ],
                'offer' => [
                    'table' => 'jbusinessdirectory_company_offers',
                    'column_id' => 'id',
                    'column_title' => 'subject',
                    'column_state' => 'state'
                ],
                'event' => [
                    'table' => 'jbusinessdirectory_company_events',
                    'column_id' => 'id',
                    'column_title' => 'name',
                    'column_state' => 'state'
                ]
            ],

            'djcatalog2' => [
                'table' => 'djc2_items',
                'column_id' => 'id',
                'column_title' => 'name',
                'column_state' => 'published'
            ],

            'djclassifieds' => [
                'table' => 'djcf_items',
                'column_id' => 'id',
                'column_title' => 'name',
                'column_state' => 'published'
            ],

            'djevents' => [
                'table' => 'djev_events',
                'column_id' => 'id',
                'column_title' => 'name',
                'column_state' => 'published'
            ],

            'easyblog' => [
                'table' => 'easyblog_post',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published',
                'where' => 'i.state = 0'
            ],

            'eventbooking' => [
                'table' => 'eb_events',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published'
            ],

            'eshop' => [
                'table' => 'eshop_productdetails',
                'column_id' => 'product_id',
                'column_title' => 'product_name',
                'column_state' => 'p.published',
                'join' => '#__eshop_products as p ON i.product_id = p.id'
            ],

            'gridbox' => [
                'table' => 'gridbox_pages',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published',
                'where' => 'i.published = 1'
            ],

            'quix' => [
                'table' => 'quix_pages',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published',
                'where' => 'i.state > 0'
            ],

            'rsblog' => [
                'table' => 'rsblog_posts',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published'
            ],

            'sobipro' => [
                'table' => 'sobipro_object',
                'column_id' => 'i.id',
                'column_title' => 'f.baseData',
                'column_state' => 'i.state',
                'join' => '#__sobipro_field_data as f ON i.id = f.sid',
                'where' => 'f.fid = 1'
            ],

            'jcalpro' => [
                'table' => 'jcalpro_events',
                'column_id' => 'id',
                'column_title' => 'title',
                'column_state' => 'published'
            ],

            'jevents' => [
                'table' => 'jevents_vevent',
                'column_id' => 'ev_id',
                'column_title' => 'summary',
                'column_state' => 'state'
            ]
        ];

        return self::$queryPresets;
    }

    /**
     * Get query preset configuration for this field
     *
     * @return array Query preset configuration
     */
    protected function getPresetConfig()
    {
        // Parse preset:variant syntax
        $parts = explode(':', $this->preset, 2);
        $presetBase = $parts[0];
        $variant = isset($parts[1]) ? $parts[1] : 'default';

        // Get preset map
        $map = self::getQueryPresetsMap();

        // Validate preset exists
        if (!isset($map[$presetBase]))
        {
            throw new \RuntimeException('ComponentItems: Invalid preset "' . $presetBase . '"');
        }

        $presetConfig = $map[$presetBase];

        // Check if preset has variants
        if (isset($presetConfig['table']))
        {
            // single variant preset
            if ($variant !== 'default')
            {
                throw new \RuntimeException('ComponentItems: Preset "' . $presetBase . '" does not support variants');
            }
            return $presetConfig;
        }

        // multi-variant preset
        if (!isset($presetConfig[$variant]))
        {
            throw new \RuntimeException('ComponentItems: Invalid variant "' . $variant . '" for preset "' . $presetBase . '"');
        }

        return $presetConfig[$variant];
    }

    /**
     * 
     * Backward compatibility methods
     * 
     * Find the preset that best matches the given old XML configuration values
     *
     * @param array $queryConfig Configuration to match (table, column_id, column_title, column_state, where, join, group)
     * @return string|null Preset identifier (e.g., 'content:published') or null if no match
     */
    public static function findPresetByConfig(array $queryConfig)
    {
        $map = self::getQueryPresetsMap();
        $fields = ['table', 'column_id', 'column_title', 'column_state', 'where', 'join', 'group'];

        $bestMatch = null;
        $bestMatchCount = 0;

        foreach ($map as $presetBase => $presetConfig)
        {
            if (isset($presetConfig['table']))
            {
                $matchCount = self::countMatchingFields($queryConfig, $presetConfig, $fields);
                if ($matchCount > $bestMatchCount) {
                    $bestMatch = $presetBase;
                    $bestMatchCount = $matchCount;
                }
            }
            else
            {
                foreach ($presetConfig as $variant => $variantConfig)
                {
                    $matchCount = self::countMatchingFields($queryConfig, $variantConfig, $fields);
                    if ($matchCount > $bestMatchCount)
                    {
                        $bestMatch = $presetBase . ':' . $variant;
                        $bestMatchCount = $matchCount;
                    }
                }
            }
        }

        return $bestMatch;
    }

    private static function countMatchingFields(array $input, array $preset, array $fields)
    {
        $count = 0;

        foreach ($fields as $field)
        {
            $inputValue = $input[$field] ?? null;
            $presetValue = $preset[$field] ?? null;

            if ($inputValue !== null && $inputValue === $presetValue)
            {
                $count++;
            }
        }

        return $count;
    }
}