<?php
/**
 * @package messenger
 * @author kputyr
 * @date 2026-02-03
 */

namespace IMATHUZH\Qfq\Core\Messenger;

use IMATHUZH\Qfq\Core\Store\Sip;

/**
 * Publishes chat events to nchan via the existing Messenger endpoint.
 *
 * Authentication uses SIP tokens - the group and pub channels are
 * included in the SIP, not in the URL path.
 *
 * URL format: POST {basepath}/{sip-token}
 * SIP contains: group={group}&pub={channel}
 */
class ChatPublisher {
    private string $channelPrefix;
    private string $basepath;
    private Sip $sip;
    private string $group;

    /**
     * @param string $basepath  The base URL for the Messenger endpoint,
     *                          e.g. "https://example.com/messenger"
     * @param Sip $sip  SIP instance for generating auth tokens
     * @param string $group  Messenger group, defaults to CHAT_NCHAN_GROUP
     * @param string $channelPrefix  Channel prefix, defaults to 'chat'
     */
    public function __construct(string $basepath, Sip $sip, string $group = CHAT_NCHAN_GROUP, string $channelPrefix = CHAT_NCHAN_CHANNEL_PREFIX) {
        $this->basepath = rtrim($basepath, '/');
        $this->sip = $sip;
        $this->group = $group;
        $this->channelPrefix = $channelPrefix;
    }

    /**
     * Build the channel name from xId and cIdTopic.
     *
     * @param int $xId  Record ID
     * @param int $cIdTopic  Chat topic ID
     * @return string  Channel name, e.g. "chat-42-1"
     */
    public function buildChannel(int $xId, int $cIdTopic): string {
        return sprintf('%s-%d-%d', $this->channelPrefix, $xId, $cIdTopic);
    }

    /**
     * Generate a SIP token for publishing to a channel.
     *
     * @param string $channel  Channel name
     * @return string  SIP token
     */
    private function generatePublishSip(string $channel): string {
        $queryString = 'group=' . $this->group . '&pub=' . $channel;
        return $this->sip->queryStringToSip($queryString, RETURN_SIP);
    }

    /**
     * Build the full publish URL with SIP token.
     *
     * @param string $channel  Channel name
     * @return string  Full URL, e.g. "https://example.com/messenger/abc123"
     */
    private function buildPublishUrl(string $channel): string {
        $sipToken = $this->generatePublishSip($channel);
        return $this->basepath . '/' . $sipToken;
    }

    /**
     * Create a JSON message in the standard messenger format.
     *
     * @param string $name  Message name/type
     * @param array $payload  Message payload
     * @return string  JSON encoded message
     */
    private function createMessage(string $name, array $payload): string {
        return json_encode([
            'type' => 'message',
            'name' => $name,
            'payload' => $payload
        ]);
    }

    /**
     * Publish a "new message" notification.
     *
     * Called after a chat message is saved to notify other clients.
     *
     * @param int $xId  Record ID
     * @param int $cIdTopic  Chat topic ID
     * @param int $cId  Message ID
     * @param bool $chatRoom  Whether this is a chat room message (vs thread)
     * @return array  Array of error strings (empty on success)
     */
    public function publishNewMessage(int $xId, int $cIdTopic, int $cId, bool $chatRoom): array {
        $channel = $this->buildChannel($xId, $cIdTopic);
        $url = $this->buildPublishUrl($channel);
        $message = $this->createMessage(
            CHAT_NCHAN_MESSAGE_PING,
            ['cId' => $cId, 'chatRoom' => $chatRoom, 'action' => 'new-message']
        );
        return $this->postMessage($url, $message);
    }

    /**
     * Publish a "toolbar update" notification.
     *
     * Called after a tag or done status changes to notify other clients
     * to refresh the toolbar.
     *
     * @param int $xId  Record ID
     * @param int $cIdTopic  Chat topic ID
     * @param int $cId  Message ID
     * @param bool $chatRoom  Whether this is a chat room message (vs thread)
     * @return array  Array of error strings (empty on success)
     */
    public function publishToolbarUpdate(int $xId, int $cIdTopic, int $cId, bool $chatRoom): array {
        $channel = $this->buildChannel($xId, $cIdTopic);
        $url = $this->buildPublishUrl($channel);
        $message = $this->createMessage(
            CHAT_NCHAN_MESSAGE_TOOLBAR_PING,
            ['cId' => $cId, 'chatRoom' => $chatRoom, 'action' => 'toolbar-update']
        );
        return $this->postMessage($url, $message);
    }

    /**
     * POST a message to the given URL.
     *
     * @param string $url  Full URL to POST to
     * @param string $message  JSON message body
     * @return array  Array of error strings (empty on success)
     */
    private function postMessage(string $url, string $message): array {
        $errors = [];
        $options = [
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/json',
                'content' => $message,
                'ignore_errors' => true,
                'user_agent' => MESSENGER_USER_AGENT
            ]
        ];
        $context = stream_context_create($options);
        $result = @file_get_contents($url, false, $context);

        if ($result === false) {
            $errors[] = "ChatPublisher: bad request to $url";
        } elseif (isset($http_response_header[0])) {
            if (preg_match('{HTTP\/\S*\s(\d{3})}', $http_response_header[0], $match)) {
                $status = (int)$match[1];
                if ($status < 200 || $status >= 300) {
                    $errors[] = "ChatPublisher: HTTP $status from $url";
                }
            }
        }
        return $errors;
    }
}
