<?php
/**
 * @package         ReReplacer
 * @version         14.4.1
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            https://regularlabs.com
 * @copyright       Copyright © 2025 Regular Labs All Rights Reserved
 * @license         GNU General Public License version 2 or later
 */

namespace RegularLabs\Plugin\System\ReReplacer;

defined('_JEXEC') or die;

use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Article as RL_Article;
use RegularLabs\Library\Php as RL_Php;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;

class Item
{
    private static $counter  = [];
    private static $splitter = '<!-- RR_REPLACE_SPLITTER -->';
    public         $replace;
    public         $search;
    public         $settings;
    private        $article;

    public function __construct($settings, &$article = null)
    {
        $this->settings = $settings;
        $this->article  = $article;
        $this->search   = $settings->search;
        $this->replace  = $settings->replace;
    }

    public function replace(&$string)
    {
        if (empty($string))
        {
            return;
        }

        if (is_array($string))
        {
            $this->replaceArray($string);

            return;
        }

        switch (true)
        {
            case ( ! $this->settings->regex && $this->settings->treat_as_list):
                $this->replaceAsList($string);
                break;

            default:
                $this->replaceAsString($string);
                break;
        }

    }

    public function replaceInArticle($context)
    {
        $is_category = ! isset($this->article->catid) && isset($this->article->parent_id);

        if ($is_category && ! $this->settings->enable_in_category)
        {
            return;
        }

        $ignore = [];

        if ( ! $this->settings->enable_in_content)
        {
            $ignore[] = 'text';
            $ignore[] = 'introtext';
            $ignore[] = 'fulltext';
        }

        if ( ! $this->settings->enable_in_title)
        {
            $ignore[] = 'title';
        }

        if ( ! $this->settings->enable_in_author)
        {
            $ignore[] = 'created_by_alias';
        }

        RL_Article::process($this->article, $context, $this, 'replace', [], $ignore);
    }

    public function setReplace($string)
    {
        $this->replace = $string;
    }

    public function setSearch($string)
    {
        $this->search = $string;
    }

    private function getCounterName()
    {
        // Counter is used to make it possible to use \# or [[counter]] in the replacement to refer to the incremental counter
        $counter_name = base64_encode($this->search . $this->replace);

        if ( ! isset(static::$counter[$counter_name]))
        {
            static::$counter[$counter_name] = 0;
        }

        return $counter_name;
    }

    private function getPhpResult($string)
    {
    }

    private function getPreparedReplaceString()
    {
        $replace = $this->replace;

        if ($replace == '')
        {
            return '';
        }

        

        Clean::cleanStringReplace($replace, $this->settings->regex);

        return $replace;
    }

    private function getPreparedSearchString()
    {
        if ($this->settings->regex)
        {
            return $this->getPreparedSearchStringRegex();
        }

        $search = $this->search;

        // remove any leading new line characters, but keep other whitespace
        $search = ltrim($search, "\r\n");

        if ($search == '')
        {
            return '';
        }

        // Prepare search string
        Clean::cleanString($search);

        $search = RL_RegEx::quote($search);

        if ($this->settings->word_search)
        {
            $search = '(?<!\p{L})(' . $search . ')(?!\p{L})';
        }

        if ($this->settings->strip_p_tags)
        {
            $search = '(?:<p(?: [^>]*)?>)?' . $search . '(?:</p>)?';
        }

        $this->prepareRegex($search, true, $this->settings->casesensitive);

        return $search;
    }

    private function getPreparedSearchStringRegex()
    {
        $search = $this->search;

        Clean::cleanString($search);

        // escape hashes
        $search = str_replace('#', '\#', $search);
        // unescape double escaped hashes
        $search = str_replace('\\\\#', '\#', $search);

        if ($this->settings->strip_p_tags)
        {
            $search = '(?:<p(?: [^>]*)?>)?' . $search . '(?:</p>)?';
        }

        $this->prepareRegex($search, $this->settings->s_modifier, $this->settings->casesensitive);

        return $search;
    }

    private function prepareRegex(&$string, $dotall = true, $casesensitive = true)
    {
        $string = '#' . $string . '#';

        $string .= $dotall ? 's' : '';        // . (dot) also matches newlines
        $string .= $casesensitive ? '' : 'i'; // case-insensitive pattern matching

        // replace new lines with regex match
        $string = str_replace(["\r", "\n"], ['', '(?:\r\n|\r|\n)'], $string);
    }

    private function replaceArray(&$array)
    {
        if ( ! is_array($array))
        {
            return;
        }

        foreach ($array as &$string)
        {
            $this->replace($string);
        }
    }

    private function replaceAsList(&$string)
    {
        $searches = RL_Array::toArray($this->search, ',', false, false);
        $replaces = $this->replace == '' ? [''] : RL_Array::toArray($this->replace, ',', false, false);

        foreach ($searches as $key => $search)
        {
            // Prepare replace string
            $replace = isset($replaces[$key]) ? $replaces[$key] : $replaces[0];
            // remove any leading new line characters, but keep other whitespace
            $replace = ltrim($replace, "\r\n");

            $item = new Item($this->settings, $this->article);
            $item->setSearch($search);
            $item->setReplace($replace);

            $item->replaceAsString($string);
        }
    }

    private function replaceAsString(&$string)
    {
        $replacer = StringReplacer::get($string, $this->settings);

        $replacer->run(function (&$string) {
            $this->replaceFinal($string);
        });

        $string = $replacer->toString();
    }

    private function replaceFinal(&$string)
    {
        $search = $this->getPreparedSearchString();

        if ($search == '' || ! RL_RegEx::match($search, $string))
        {
            return;
        }

        $replace = $this->getPreparedReplaceString();

        // Do a simple replace if not thorough, not using a max and counter is not found
        if (
            $this->settings->thorough
            || $this->settings->max_replacements
            || str_contains($replace, '[[counter]]')
            || str_contains($replace, '\#')
        )
        {
            $this->replaceFinalUsingCounter($string, $search, $replace);

            return;
        }

        $string = RL_RegEx::replace($search, $replace, $string);

    }

    private function replaceFinalUsingCounter(&$string, $search, $replace)
    {
        if ($this->settings->max_replacements)
        {
            $this->settings->thorough = false;
        }

        $counter_name = $this->getCounterName();

        $thorough_count = 1; // prevents the thorough search to repeat endlessly
        $count          = RL_RegEx::matchAll($search, $string);

        while ($count)
        {
            if ($this->settings->max_replacements > 0 && static::$counter[$counter_name] >= $this->settings->max_replacements)
            {
                break;
            }

            if ($this->settings->max_replacements > 0 && (static::$counter[$counter_name] + $count) > $this->settings->max_replacements)
            {
                $count = $this->settings->max_replacements - static::$counter[$counter_name];
            }

            $replace_loop = $replace;
            static::replaceOccurrence($search, $replace_loop, $string, $count, $counter_name);


            if ( ! $this->settings->thorough)
            {
                break;
            }

            if (++$thorough_count >= 100)
            {
                break;
            }

            $count = RL_RegEx::matchAll($search, $string);
        }
    }

    private function replaceOccurrence($search, $replace, &$string, $count = 0, $counter_name = '')
    {
        if ( ! $counter_name)
        {
            $string = RL_RegEx::replace($string, '', $count ?: -1);

            return;
        }

        for ($i = 0; $i < $count; $i++)
        {
            // Replace \# with the incremental counter
            $replace_loop = str_replace(['\#', '[[counter]]'], ++static::$counter[$counter_name], $replace);

            // Replace with offset
            RL_RegEx::match($search, $string, $matches, null, PREG_OFFSET_CAPTURE);

            $substring          = substr($string, $matches[0][1]);
            $substring_replaced = RL_RegEx::replaceOnce($search, $replace_loop, $substring);

            $string = str_replace($substring, $substring_replaced, $string);
        }
    }

    private function replacePhp(&$string)
    {
    }
}
