<?php
/**
 * Chatbot CLI streaming event emitter.
 *
 * @package KimaAI\ChatBot
 */

declare(strict_types=1);

namespace KimaAI\ChatBot;

defined('ABSPATH') || exit;

/**
 * Emits chatbot streaming events in a WP-CLI friendly format.
 */
class ChatbotCliSseEmitter extends ChatbotSseEmitter {

  /**
   * Whether assistant text has been printed to stdout.
   *
   * @var bool
   */
  private bool $wrote_text = false;

  /**
   * Create the CLI emitter.
   *
   * @param bool $raw Whether to print structured JSON line events.
   */
  public function __construct(private bool $raw = false) {
    parent::__construct(false);
  }

  /**
   * Start a CLI stream without HTTP headers or output buffer changes.
   *
   * @return void
   */
  public function start(): void {
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Match the HTTP emitter while staying tolerant of locked-down PHP configs.
    @set_time_limit(0);
  }

  /**
   * Send one streaming event to the CLI.
   *
   * @param array $data Event payload.
   * @param string $event Event name.
   * @return void
   */
  public function send(array $data, string $event = 'message'): void {
    if ($this->raw) {
      \WP_CLI::line(
        (string) wp_json_encode(
          [
            'event' => $event,
            'data'  => $data,
          ],
          JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
        )
      );
      return;
    }

    $type = (string) ($data['type'] ?? '');

    if ($event === 'delta' || $type === 'delta') {
      $text = (string) ($data['text'] ?? '');
      if ($text !== '') {
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI streaming deltas must be printed verbatim.
        echo $text;
        $this->wrote_text = true;
        $this->flushOutput();
      }
      return;
    }

    if ($event === 'tool' || $type === 'tool') {
      $status = (string) ($data['status'] ?? 'progress');
      $name = (string) ($data['name'] ?? 'tool');
      $this->writeErrorLine(sprintf('[tool:%s] %s', $status, $name));
      return;
    }

    if ($event === 'error' || $type === 'error') {
      $this->writeErrorLine((string) ($data['message'] ?? __('Something went wrong.', 'kimaai')));
    }
  }

  /**
   * Send a normalized error event.
   *
   * @param array $normalized Normalized provider error.
   * @return void
   */
  public function error(array $normalized): void {
    $this->send(
      [
        'type'    => 'error',
        'message' => (string) ($normalized['userMessage'] ?? __('Something went wrong.', 'kimaai')),
        'error'   => $normalized,
      ],
      'error'
    );
  }

  /**
   * Finish the CLI stream.
   *
   * @return void
   */
  public function done(): void {
    if ($this->raw) {
      \WP_CLI::line(
        (string) wp_json_encode(
          [
            'event' => 'done',
            'data'  => '[DONE]',
          ]
        )
      );
      return;
    }

    if ($this->wrote_text) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This only terminates streamed CLI text.
      echo PHP_EOL;
      $this->flushOutput();
    }
  }

  /**
   * Write a status line to stderr when available.
   *
   * @param string $message Message to print.
   * @return void
   */
  private function writeErrorLine(string $message): void {
    if (defined('STDERR')) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- WP-CLI status output belongs on STDERR.
      fwrite(STDERR, $message . PHP_EOL);
      fflush(STDERR);
      return;
    }

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI status output is not HTML.
    echo $message . PHP_EOL;
    $this->flushOutput();
  }

  /**
   * Flush stdout for streaming output.
   *
   * @return void
   */
  private function flushOutput(): void {
    if (function_exists('flush')) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Flush can warn under some SAPIs and is non-critical for CLI.
      @flush();
    }
  }
}
