<?php
/**
 * Chatbot server-sent event emitter.
 *
 * @package KimaAI\ChatBot
 */

declare(strict_types=1);

namespace KimaAI\ChatBot;

defined('ABSPATH') || exit;

class ChatbotSseEmitter {

  /**
   * Create the emitter.
   *
   * @param bool $terminate_on_done Whether to terminate the process when the stream is done.
   */
  public function __construct(private bool $terminate_on_done = true) {
  }

  /**
   * Start the SSE stream by sending HTTP headers and disabling output buffering.
   *
   * @return void
   */
  public function start(): void {
    if (!headers_sent()) {
      status_header(200);
      header('Content-Type: text/event-stream; charset=utf-8');
      header('Cache-Control: no-cache, no-transform');
      header('Connection: keep-alive');
      header('X-Accel-Buffering: no');
    }

    $this->disableOutputBuffering();
    @set_time_limit(0); // phpcs:ignore
    @ignore_user_abort(true); // phpcs:ignore
  }

  /**
   * Send a data event to the client.
   *
   * @param array $data Event payload.
   * @param string $event Event name.
   * @return void
   */
  public function send(array $data, string $event = 'message'): void {
    echo 'event: ' . $event . "\n";
    echo 'data: ' . wp_json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n\n";
    $this->flush();
  }

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

  /**
   * Send the terminal [DONE] event and optionally terminate the process.
   *
   * @return void
   */
  public function done(): void {
    echo "data: [DONE]\n\n";
    $this->flush();

    if ($this->terminate_on_done) {
      exit;
    }
  }

  /**
   * Flush output buffers to push data to the client immediately.
   *
   * @return void
   */
  private function flush(): void {
    if (function_exists('ob_flush') && ob_get_level() > 0) {
      @ob_flush(); // phpcs:ignore
    }
    if (function_exists('flush')) {
      @flush(); // phpcs:ignore
    }
  }

  /**
   * Disable PHP output buffering to allow unbuffered SSE streaming.
   *
   * @return void
   */
  private function disableOutputBuffering(): void {
    while (ob_get_level() > 0) {
      @ob_end_flush(); // phpcs:ignore
    }

    // phpcs:ignore -- Required to disable output buffering for SSE streaming.
    @ini_set('output_buffering', 'off');
    // phpcs:ignore -- Required to disable zlib compression for unbuffered SSE streaming.
    @ini_set('zlib.output_compression', '0');
  }
}
