<?php

declare(strict_types=1);

namespace App\Agent;

/**
 * ThinkSuppressor
 *
 * Robust streaming-safe suppressor for internal <think>...</think> sections.
 *
 * Key properties:
 * - Handles token fragmentation (partial tags across tokens)
 * - Stateful per stream, stateless per request
 * - Does not buffer full responses
 * - Deterministic and predictable
 */
final class ThinkSuppressor
{
    /** Indicates whether the stream is currently inside a <think> block. */
    private bool $insideThink = false;

    /** Indicates whether the think section has been fully closed. */
    private bool $thinkSectionCompleted = false;

    /**
     * Rolling buffer for detecting fragmented tags across tokens.
     */
    private string $rollingBuffer = '';

    /**
     * Maximum buffer length needed to safely detect tags.
     */
    private int $maxBufferLength = 32;

    /**
     * Filters a single token from the LLM stream.
     *
     * @param string $token Raw token from the LLM
     * @return string Cleaned token safe for user output
     */
    public function filter(string $token): string
    {
        // Append to rolling buffer
        $this->rollingBuffer .= $token;
        if (strlen($this->rollingBuffer) > $this->maxBufferLength) {
            $this->rollingBuffer = substr($this->rollingBuffer, -$this->maxBufferLength);
        }

        // If think section is already completed, just strip stray closing tags
        if ($this->thinkSectionCompleted) {
            return str_replace('</think>', '', $token);
        }

        // Detect fragmented opening <think> tag
        if (!$this->insideThink && str_contains($this->rollingBuffer, '<think>')) {
            $this->insideThink = true;
            return '';
        }

        // Detect fragmented closing </think> tag
        if ($this->insideThink && str_contains($this->rollingBuffer, '</think>')) {
            $this->insideThink = false;
            $this->thinkSectionCompleted = true;

            // Emit a single line break after think section ends
            return "\n";
        }

        // Suppress all content while inside <think>...</think>
        if ($this->insideThink) {
            return '';
        }

        return $token;
    }

    /**
     * Resets the suppressor state.
     * Must be called before starting a new stream.
     */
    public function reset(): void
    {
        $this->insideThink = false;
        $this->thinkSectionCompleted = false;
        $this->rollingBuffer = '';
    }
}
