<?php
/**
 * @package         Quick Index
 * @version         3.4.0PRO
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://regularlabs.com
 * @copyright       Copyright © 2023 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Plugin\System\QuickIndex;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use Joomla\CMS\Table\Table as JTable;
use Joomla\Component\Content\Site\Helper\RouteHelper as JContentRouteHelper;
use Joomla\Registry\Registry;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Article as RL_Article;
use RegularLabs\Library\Cache as RL_Cache;
use RegularLabs\Library\Protect as RL_Protect;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use RegularLabs\Library\Uri as RL_Uri;

class Replace
{
    static $counter           = 1;
    static $indexes_to_remove = [];

    public static function addAutoIndex($area, $context, &$article)
    {
        if ($area === 'other' && $context == 'text')
        {
            if (empty($article->text) || ! RL_String::contains($article->text, Params::getTags(true)))
            {
                return;
            }

            $regex = Params::getRegex();
            RL_RegEx::matchAll($regex, $article->text, $matches);

            foreach ($matches as $match)
            {
                $settings = new Settings($match['settings']);
                if ($settings->get('article') == 'current')
                {
                    Params::setAutoIndex();
                    break;
                }
            }

            return;
        }

        if ($area != 'article' || $context != 'com_content.article')
        {
            return;
        }

        if ( ! $article)
        {
            return;
        }

        $params = Params::get();

        if ( ! $params->auto_index)
        {
            return;
        }

        if (empty($article->catid) || ! isset($article->text))
        {
            return;
        }

        if ( ! self::passCategories($article->catid))
        {
            return;
        }

        if (RL_String::contains($article->text, Params::getTags(true)))
        {
            return;
        }

        // Tag character start and end
        [$tag_start, $tag_end] = Params::getTagCharacters();

        $article->text = '<!-- START: AUTO INDEX -->' . $tag_start . $params->tag . $tag_end . '<!-- END: AUTO INDEX -->'
            . $article->text;
    }

    public static function addIndexToCorrectPage($html, $index, $page = 0)
    {
        $index_html = $index->get();
        $index->replaceInContents($html);

        $index_html = RL_Protect::wrapInCommentTags('Quick Index: ' . md5($index_html), $index_html);

        if ( ! $page || ! $index->headings->hasPages())
        {
            return $index_html . $html;
        }

        $pages = RL_Article::getPages($html);

        $start_page = $page - $index->headings->getFirstPage();

        if ( ! isset($pages[$start_page * 2]))
        {
            return $index_html . $html;
        }

        $pages[$start_page * 2] = $index_html . $pages[$start_page * 2];

        return implode('', $pages);
    }

    public static function getParentCategoryIds($id)
    {
        if ( ! $id)
        {
            return [];
        }

        $cache = new RL_Cache;

        if ($cache->exists())
        {
            return $cache->get();
        }

        $parent_ids = [];

        $db = JFactory::getDbo();

        while ($id)
        {
            $query = $db->getQuery(true)
                ->select('t.parent_id')
                ->from('#__categories as t')
                ->where('t.id = ' . (int) $id);
            $db->setQuery($query);
            $id = $db->loadResult();

            // Break if no parent is found or parent already found before for some reason
            if ( ! $id || in_array($id, $parent_ids))
            {
                break;
            }

            $parent_ids[] = $id;
        }

        return $cache->set($parent_ids);
    }

    public static function moveAutoIndexToPage(&$string, $page = 0)
    {
        if ( ! $page)
        {
            return;
        }

        if ( ! RL_RegEx::match('#<\!-- START: AUTO INDEX -->(?<contents>.*?)<!-- END: AUTO INDEX -->#', $string, $match))
        {
            return;
        }

        $pages = RL_Article::getPages($string);

        if ( ! isset($pages[$page * 2]))
        {
            return;
        }

        $pages[$page * 2] = $match['contents'] . $pages[$page * 2];

        $string = implode('', $pages);
        $string = str_replace($match[0], '', $string);
    }

    public static function replaceTags(&$string, $area = 'article', $context = '', $page = 0)
    {
        if ( ! is_string($string) || $string == '')
        {
            return false;
        }

        if ( ! RL_String::contains($string, Params::getTags(true)))
        {
            $string = self::removeIndexesFromArticle($string);

            return false;
        }

        // Check if tags are in the text snippet used for the search component
        if (strpos($context, 'com_search.') === 0)
        {
            $limit = explode('.', $context, 2);
            $limit = (int) array_pop($limit);

            $string_check = substr($string, 0, $limit);

            if ( ! RL_String::contains($string_check, Params::getTags(true)))
            {
                return false;
            }
        }

        $params = Params::get();
        $regex  = Params::getRegex();

        // allow in component?
        if (RL_Protect::isRestrictedComponent($params->disabled_components ?? [], $area))
        {
            if ( ! $params->disable_components_remove)
            {
                Protect::protectTags($string);

                return true;
            }

            Protect::_($string);

            $string = RL_RegEx::replace(Params::getRegexStartTag(), '<!-- QI: START -->\0', $string);

            $string = RL_RegEx::replace($regex, '\3', $string);

            RL_Protect::unprotect($string);

            return true;
        }

        Protect::_($string);

        $string = RL_RegEx::replace(Params::getRegexStartTag(), '<!-- QI: START -->\0', $string);

        RL_RegEx::matchAll($regex, $string, $matches);

        foreach ($matches as $match)
        {
            self::replaceTag($string, $match, $area, $page);
        }

        $string = self::removeIndexesFromArticle($string);

        RL_Protect::unprotect($string);

        return true;
    }

    private static function getArticle($id = '')
    {
        if (empty ($id))
        {
            return false;
        }

        if ($id == 'current')
        {
            $id = RL_Article::getId();
        }

        $article = RL_Article::get($id);

        if ( ! $article || ! isset($article->id))
        {
            return false;
        }

        $article_id = $article->id;

        $article = JTable::getInstance('Content', 'JTable');
        $article->load(['id' => $article_id]);

        if (empty($article->id))
        {
            return false;
        }

        $article->introtext ??= '';
        $article->fulltext  ??= '';
        $article->text      = ($article->text ?? null) ?: $article->introtext . $article->fulltext;
        $article->slug      ??= '';
        $article->catslug   ??= '';

        $article_params = new Registry;
        $article_params->loadArray(['inline' => false]);

        JPluginHelper::importPlugin('content');

        JFactory::getApplication()->triggerEvent(
            'onContentPrepare',
            ['com_content.article', &$article, &$article_params, 0]
        );

        return $article;
    }

    private static function grabIndexFromArticle($article_id, $index_nrs = '')
    {
        $index = (object) [
            'contents' => [],
            'ids'      => [],
        ];

        $article = self::getArticle($article_id);

        if ( ! $article || empty($article->text))
        {
            return $index;
        }

        $text = $article->text;

        if ( ! RL_RegEx::matchAll('#<\!-- START: Quick Index: (?<id>[0-9a-f]+) -->(?<contents>.*?)<!-- END: Quick Index: \1 -->#', $text, $matches))
        {
            return $index;
        }

        if ( ! class_exists('ContentHelperRoute'))
        {
            require_once JPATH_SITE . '/components/com_content/helpers/route.php';
        }

        $params    = Params::get();
        $index_nrs = RL_Array::toArray($index_nrs);

        foreach ($matches as $i => $match)
        {
            $nr = $i + 1;

            if ( ! empty($index_nrs) && ! in_array($nr, $index_nrs))
            {
                continue;
            }

            $id       = $match['id'];
            $contents = $match['contents'];

            $url = JContentRouteHelper::getArticleRoute($article->id, $article->get('catid'), $article->get('language'));

            RL_RegEx::matchAll('href="[^"]*(?:[\?&]start=(?<page>[0-9]*))\#(?<hash>[^"]*)" class="index-link"', $contents, $url_matches);

            foreach ($url_matches as $url_match)
            {
                if ( ! empty($url_match['page']))
                {
                    $url = RL_Uri::removeParameter($url, 'showall');
                    $url = RL_Uri::addParameter($url, 'start', $url_match['page']);
                }

                $url = RL_Uri::appendHash($url, $url_match['hash']);

                $contents = RL_String::replaceOnce(
                    $url_match[0],
                    'href="' . $url . '" class="index-link"',
                    $contents
                );
            }

            if ($params->place_comments)
            {
                $contents = RL_Protect::wrapInCommentTags('Quick Index', $contents);
            }

            $index->contents[$nr] = $contents;
            $index->ids[]         = $id;
        }

        $index->contents = RL_Array::sortByOtherArray($index->contents, $index_nrs);

        return $index;
    }

    private static function passCategories($needle)
    {
        $params = Params::get();

        $include_type = $params->auto_index_categories_include ?? false;

        if ( ! $include_type)
        {
            return true;
        }

        $haystack         = $params->auto_index_categories ?? [];
        $include_children = $params->auto_index_categories_include_children ?? false;
        $exclude          = $include_type === '2';

        if (empty($needle))
        {
            return $exclude;
        }

        if ($include_children === '2'
            && RL_Array::find($needle, $haystack)
        )
        {
            return $exclude;
        }

        $needles = [$needle];

        if ($include_children)
        {
            $needles = array_merge($needles, self::getParentCategoryIds($needle));
        }

        if ( ! RL_Array::find($needles, $haystack))
        {
            return $exclude;
        }

        return ! $exclude;
    }

    private static function removeIndexesFromArticle($string)
    {
        if (empty(self::$indexes_to_remove))
        {
            return $string;
        }

        RL_Protect::unprotect($string);

        if ( ! RL_String::contains($string, self::$indexes_to_remove))
        {
            return $string;
        }

        $string = RL_RegEx::replace(
            '<\!-- START: Quick Index: (' . RL_RegEx::quote(self::$indexes_to_remove) . ') -->.*?<!-- END: Quick Index: \1 -->',
            '',
            $string
        );

        Protect::_($string);

        return $string;
    }

    private static function replaceTag(&$string, $match, $area = 'article', $page = 0)
    {
        $contents = $match['text'];

        $settings = new Settings($match['settings']);

        // article attribute found before articles are rendered
        // ignore it and handle later
        if ($settings->get('article') && $area == 'article')
        {
            return;
        }

        if ($settings->get('article') == 'current' && $area == 'other')
        {
            return;
        }

        // article attribute set, so move index from that article
        if ($settings->get('article'))
        {
            $index    = self::grabIndexFromArticle($settings->get('article'), $settings->get('index'));
            $contents = implode('', $index->contents) . $contents;

            $string = str_replace($match[0], $contents, $string);

            self::$indexes_to_remove = array_merge(self::$indexes_to_remove, $index->ids);

            return;
        }

        // leave indexes with ending tag to latest body rendering
//        if ($area != 'html' && $match['endtag'])
//        {
//            return;
//        }

        // {index} tag found outside the component with no ending tag.
        // Can't handle that, so remove it
        if ($area === 'html' && ! $match['endtag'])
        {
            $string = str_replace($match[0], $contents, $string);

            return;
        }

        $all_pages     = RL_Article::getPages($string);
        $content_pages = RL_Article::getPages($contents);
        $start_page    = RL_Article::getPageNumber($all_pages, $match['tag'] . $content_pages[0]);

        $index = new Index($settings, $contents, $start_page);

        $html = self::addIndexToCorrectPage($contents, $index, $page);

        if ( ! empty($match['post']))
        {
            $html .= $match['post'];
        }

        $string = str_replace($match[0], $html, $string);
    }
}
