<?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 ContentHelperRoute;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Uri\Uri as JUri;
use RegularLabs\Library\Article as RL_Article;
use RegularLabs\Library\HtmlTag as RL_HtmlTag;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use RegularLabs\Library\Uri as RL_Uri;

class Headings
{
    var $article;
    var $headings;
    var $html;
    var $level_counts   = [];
    var $level_texts    = [];
    var $previous_level = 0;
    var $settings;
    var $start_page;
    var $used_ids       = [];

    public function __construct(Settings $settings, $html, $start_page = 0)
    {
        $this->settings   = $settings;
        $this->html       = $html;
        $this->start_page = $start_page;

        $this->headings = $this->getFromString($html);
    }

    public function get()
    {
        if (empty($this->headings))
        {
            return $this->headings;
        }

        foreach ($this->headings as &$heading)
        {
            $heading = (new Heading($this->settings))->setData($heading);
        }

        $this->previous_level = $this->getLowestLevel();
        $has_pages            = $this->hasPages();

        foreach ($this->headings as &$heading)
        {
            $this->setLevels($heading);

            $heading->number = $this->level_counts;
            $heading->id     = $this->createUniqueId($heading);

            $url = $this->getUrl();
            if ($has_pages)
            {
                $url = RL_Uri::removeParameter($url, 'showall');

                $url = $heading->page > 0
                    ? RL_Uri::addParameter($url, 'start', $heading->page)
                    : RL_Uri::removeParameter($url, 'start');
            }

            $url = RL_Uri::appendHash($url, $heading->id);

            $heading->url = $url;

            $this->previous_level = $heading->level;
        }

        return $this->headings;
    }

    public function getArticleUrl()
    {
        if ( ! $this->article)
        {
            return '';
        }

        if (isset($this->article->url))
        {
            return $this->article->url;
        }

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

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

        $this->article->url = ContentHelperRoute::getArticleRoute($this->article->id, $this->article->catid, $this->article->language);

        return $this->article->url;
    }

    public function getFirstPage()
    {
        return $this->headings[0]->page;
    }

    public function getLastPage()
    {
        return $this->headings[count($this->headings) - 1]->page;
    }

    public function getUrl()
    {
        $url = $this->getArticleUrl();
        if ($url)
        {
            return $url;
        }

        $uri = JUri::getInstance();

        return JRoute::link('site', $uri->toString(['path', 'query', 'fragment']));
    }

    public function hasClass($class, $classes)
    {
        if (empty($class))
        {
            return false;
        }

        $intersect = array_intersect(explode(' ', $class), $classes);

        return ! empty($intersect);
    }

    public function hasPages()
    {
        return $this->getFirstPage() < $this->getLastPage();
    }

    public function replace(&$string)
    {
        foreach ($this->headings as $heading)
        {
            /* @var Heading $heading */
            if ( ! $heading->isInHeadingLevels($heading))
            {
                continue;
            }

            $text = '<span class="heading_text">' . $heading->text . '</span>';

            if (
                ! $heading->isTitle()
                && (
                    $this->settings->get('add_number_to_headings') == 1
                    || ($this->settings->get('add_number_to_headings') == 2 && $this->settings->get('ordered') > 0)
                )
            )
            {
                $text = '<span class="' . $this->settings->get('class_numbers') . '">'
                    . $heading->getLevelNumber() . $this->settings->get('number_separator')
                    . '</span> '
                    . $text;
            }

            if ($this->settings->get('add_heading_links')
                && strpos($text, '</a>') === false
            )
            {
                $text = '<a href="' . $heading->url . '" class="heading_link">' . $text . '</a>';
            }

            if ($this->settings->get('add_anchor_elements'))
            {
                $text = '<span id="' . $heading->id . '" class="anchor"></span>' . $text;
            }

            $new_heading =
                '<h' . trim($heading->level . ' ' . RL_HtmlTag::flattenAttributes($heading->attributes)) . '>'
                . $text
                . '</h' . $heading->level . '>';

            $string = RL_String::replaceOnce($heading->match, $new_heading, $string);
        }
    }

    private function createUniqueId(Heading $heading)
    {
        $id = $heading->getId(
            $this->settings->get('add_parents_to_ids')
                ? $this->level_texts
                : [$this->level_texts[$heading->level]]
        );

        $final_id = $id;

        $i = 1;

        while (in_array($final_id, $this->used_ids))
        {
            $final_id = $id . '-' . ++$i;
        }

        $this->used_ids[] = $final_id;

        return $final_id;
    }

    private function getFromString(&$string)
    {
        $regex = '<(h([' . $this->settings->min_headings_level . '-' . $this->settings->max_headings_level . '])(?:\s[^>]*)?)>(.*?)</h\2>';

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

        if (empty($matches))
        {
            return [];
        }

        $headings = [];

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

        foreach ($matches as $match)
        {
            $level      = $match[2];
            $attributes = RL_HtmlTag::getAttributes($match[1]);

            // ignore heading if class contains 'noindex'
            if ( ! empty($attributes['class'])
                && $this->hasClass($attributes['class'], $this->settings->get('noindex_classes'))
            )
            {
                continue;
            }

            $headings[] = (object) [
                'match'      => $match[0],
                'level'      => $level,
                'text'       => $match[3],
                'attributes' => $attributes,
                'page'       => $this->start_page + RL_Article::getPageNumber($pages, $match[0]),
            ];
        }

        return $headings;
    }

    private function getLowestLevel()
    {
        // Just some high number, higher than what is possible
        $lowest_level = 10;

        foreach ($this->headings as $heading)
        {
            $lowest_level = min($lowest_level, $heading->level);
        }

        return $lowest_level;
    }

    private function setLevels(Heading $heading)
    {
        $level = $heading->level;
        $text  = $heading->text;

        // remove any higher levels
        foreach ($this->level_counts as $key => $value)
        {
            if ($key <= $level)
            {
                continue;
            }

            unset($this->level_counts[$key]);
            unset($this->level_texts[$key]);
        }

        if ( ! isset($this->level_counts[$level]))
        {
            $this->level_counts[$level] = 0;
        }

        $this->level_texts[$level] = $text;

        if ($heading->isTitle())
        {
            return;
        }

        $this->level_counts[$level]++;
    }
}
