<?php declare(strict_types=1);
/**
 * @package messenger
 * @author kputyr
 * @date 20.04.2026
 */

// Create tokens

require_once __DIR__ .'/hooks/common.php';

use function IMATHUZH\Messenger\Example\loadTokenStorage;
use IMATHUZH\Messenger\AuthorizationToken;

$storage = loadTokenStorage();
$tokenCtl = $storage->saveToken(AuthorizationToken::createControllerToken('test', 'ch1,ch2,ch3,ch4'));
$tokenPub = $storage->saveToken(AuthorizationToken::createPublisherToken('test', '*'));
$tokenMsg1 = $storage->saveToken(AuthorizationToken::createMessengerToken('test', 'ch1,ch2', 'ch3,ch4'));
$tokenMsg2 = $storage->saveToken(AuthorizationToken::createMessengerToken('test', 'ch3,ch4', 'ch1,ch2'));

?>
<html>
<head>
<title>Messenger example</title>
<link rel="stylesheet" type="text/css" href="./controller.css">
</head>
<body>
<!-- The controller -->
<form class="channel" id="controller">
    <h1>Controller</h1>
    <div class="channel-status"><span class="status-content"></span>
        <div class="buttons"><input type="button" value="Clear" name="clear"><input type="button" value="Connect" name="connect"></div>
    </div>
    <pre class="output"></pre>
    <div class="input">
        <div>Select channels:
            <label><input type="checkbox" name="ch1">ch1</label>
            <label><input type="checkbox" name="ch2">ch2</label>
            <label><input type="checkbox" name="ch3">ch3</label>
            <label><input type="checkbox" name="ch4">ch4</label>
            <label><input type="checkbox" name="table">as table</label>
            <label><input type="checkbox" name="page">as page</label>
            <label><input type="checkbox" name="error">as error</label>
        </div>
	    <input type="text" name="name" placeholder="name" value="test">
        <textarea></textarea>
        <input type="button" name="message" value="Send">
        <input type="button" name="post" value="Post">
    </div>
</form><br>
<!-- Left websocket -->
<form class="channel" id="ws1">
    <h1>Pub ch1 ch2 - Sub ch3 ch4</h1>
    <div class="channel-status"><span class="status-content"></span>
        <div class="buttons"><input type="button" value="Clear" name="clear"><input type="button" value="Connect" name="connect"></div>
    </div>
    <pre class="output"></pre>
    <div class="input">
	<input type="text" name="name" placeholder="name" value="test">
        <textarea></textarea>
        <input type="button" name="message" value="Message">
        <input type="button" name="request" value="Request">
    </div>
</form>
<!-- Right websocket -->
<form class="channel" id="ws2">
    <h1>Pub ch3 ch4 - Sub ch1 ch2</h1>
    <div class="channel-status"><span class="status-content"></span>
        <div class="buttons"><input type="button" value="Clear" name="clear"><input type="button" value="Connect" name="connect"></div>
    </div>
    <pre class="output"></pre>
    <div class="input">
	<input type="text" name="name" placeholder="name" value="test">
        <textarea></textarea>
        <input type="button" name="message" value="Message">
        <input type="button" name="request" value="Request">
    </div>
</form>
<section id="guide">
<h1>Quick guide</h1>
<dl>
    <dt>Form elements</dt>
    <dd><ul>
        <li>Sub-title bar show current connection status.</li>
        <li>Text area is for message content, the text input above it for the message name.</li>
        <li>Controller: select channels to which a message is to be sent.</li>
        <li>Controller: <kbd>Send</kbd> uses messanger and <kbd>Post</kbd> uses Fetch API to post a message.</li>
        <li>Client: <kbd>Message</kbd> sends a message and <kbd>Request</kbd> sends a request.</li>
    </ul></dd>
    <dt>Special commands</dt>
    <dd><ul>
        <li><kbd>Request</kbd> triggers a reaction on the other client. Sent a JSON object with
            <kbd>progress</kbd> and <kbd>response</kbd> to relay the notification and response:
            <pre><code>{ "query":"Tell me something", "progress":1000, "response":1000 }</code></pre>
        </li>
        <li>Use <kbd>style</kbd> name with <kbd>color</kbd> parameter in the content to change the text color in the title bar of the other client:
            <pre><code>{ "color":"salmon" }</code></pre>
        </li>
    </ul></dd>
</dl>
</section>
</body>
<script type="module">

// For the page
import { Controller } from './controller.js';
import { Messenger, UrlFactory, ControllerUrlFactory } from './js/index.js';

const statusToString = ['waiting', 'connecting', 'configuring', 'connected', 'closing', 'closed'];

/**
 *  Assigns a messenger to a controller form and defines how messages are handled
 */
function assign(form, msgr) {
    form.messenger = msgr;
    const ctl = new Controller(form);
    const prompt = `[${form.id}]`;
    // Listen to messenger events
    msgr.onstatechange = evt => {
        const status = statusToString[evt.currentValue];
        ctl.status = status;
        ctl.post({
            type: 'event',
            channel: `status ${statusToString[evt.previousValue]} -> ${status}`
        });
        if (msgr.desiredState === Messenger.CLOSED) {
            form.querySelector('input[name=connect]').value = 'Connect';
        }
    };
    msgr.onopen = evt => ctl.post({ type: 'event', channel: 'open' });
    msgr.onclose = evt => ctl.post({ type: 'event', channel: 'close' });
    // Handle messages and requests
    msgr.subscribe({
        // Messages are handled at hand by posting them in the associated
        // controller form
        handleMessage(msg) { ctl.post(msg); },
        // Requests are handled asynchronously according to its content:
        // - progress message interval: request.payload.progress
        // - response delay:            request.payload.response
        async handleRequest(request) {
            // show the request on the controller
            ctl.post({
                type: 'request',
                timestamp: request.timestamp,
                channel: request.channel,
                payload: {
                    id: request.requestId,
                    reply: request.requester,
                    payload: request.payload
                }
            });
            let stage = 'initial';
            try {
                // send a delayed progress message
                await new Promise(res => setTimeout(res, request.payload.progress | 0));
                ctl.post({ type: 'ctl', timestamp: Date.now(), name: 'progress sent' })
                stage = 'progress';
                await request.notify('Will do!');
                // send a delayed response message
                await new Promise(res => setTimeout(res, request.payload.response | 0));
                ctl.post({ type: 'ctl', timestamp: Date.now(), name: 'response sent' })
                stage = 'response';
                await request.respond('Already done!');
            } catch(error) {
                ctl.handleError(error, 'failed at ' + stage);
            }
        }
    })
    // [Connect] clicked: open the messenger and react to the result
    ctl.onopen = () => msgr.open().then(
        console.info.bind(console, prompt, 'opened'),       // success
        console.error.bind(console, prompt, 'not opened')   // failure
    );
    // [Close] clicked: close the messenger and react to the result
    ctl.onclose = () => msgr.close().then(
        console.info.bind(console, prompt, 'closed'),       // success
        console.error.bind(console, prompt, 'not closed')   // failure
    );
    // [Message] clicked: send the message using the messenger
    ctl.onmessage = (name, payload, channels) => msgr.publish(
        name, payload, { channels }
    ).then(
        () => ctl.post({ type: 'ctl', timestamp: Date.now(), name: 'message sent' }),
        error => ctl.handleError(error, 'message failed')
    );
    // [Request] clicked: make the request and wait for a response
    ctl.onrequest = async (name, payload, channels) => {
        try {
            // Send the request message over the messenger
            const response = await msgr.request(name, payload, { channels });
            ctl.post({ type: 'ctl', timestamp: Date.now(), name: 'request sent' })
            // Listen to progress notifications
            response.onprogress = evt => ctl.post({ name, payload: evt.data });
            // Show the result once received
            // response.payload() resolves when the response is received
            ctl.post({ type: 'response', name, payload: await response.payload() })
        } catch(error) {
            // Sending a request has failed
            ctl.handleError(error, 'request failed');
        }
    };
    ctl.status = statusToString[msgr.state];
}

/**
 * An additional subscriber to the messenger that reacts to requests
 * to change the color of the title bar
 */
function titleUpdater(form, msgr, channels) {
    const titleStyle = form.querySelector('h1').style;
    // change color based on status
    const colorForState = [
        'gray',                  // waiting
        'rgb(235, 196, 39)',     // connecting
        'rgb(245, 220, 20)',     // initializing
        'blue',                  // connected
        'rgb(168, 68, 11)',      // closing
        'navy'                   // closed
    ];
    msgr.addEventListener('statechange', evt => {
        titleStyle.backgroundColor = colorForState[evt.currentValue];
    })
    // react to messages
    msgr.subscribe({
        handleMessage({ name, payload } = {}) {
            if (name === 'style') {
                console.log(`[${form.id}-title]`, payload);
                if (payload.color) titleStyle.color = payload.color;
            }
        }
    }, channels)
}

// const roomUrlFactory = new RedirectUrlFactor('test', 'listener');
// const controllerUrlFactory = new RedirectUrlFactor('test', 'controller');

const baseMessengerUrl = new URL('/messenger/', location.href);
const messengerGroup = 'test';

const ctl = new Messenger(
    new ControllerUrlFactory(baseMessengerUrl, messengerGroup, { sub: 'ch1,ch2,ch3,ch4' }),
    { logger: console }
);
ctl.tokens.add(
    '<?php echo $tokenCtl; ?>',
    '<?php echo $tokenPub; ?>'
);
assign(document.forms[0], ctl);
ctl.open();

// First WebSocket
const msgr1 = new Messenger(
    new UrlFactory(baseMessengerUrl, messengerGroup, { pub: 'ch1,ch2', sub: 'ch3,ch4' }),
    { logger: console }
);
msgr1.tokens.add('<?php echo $tokenMsg1; ?>');
assign(document.forms[1], msgr1);
titleUpdater(document.forms[1], msgr1, ['ch3']);

// Second WebSocket
const msgr2 = new Messenger(
    new UrlFactory(baseMessengerUrl, messengerGroup, { sub: 'ch1,ch2', pub: 'ch3,ch4' }),
    { logger: console, pingInterval: 0 }
);
msgr2.tokens.add('<?php echo $tokenMsg2; ?>');
assign(document.forms[2], msgr2);
titleUpdater(document.forms[2], msgr2, ['ch1']);

window.messengers = { ctl, msgr1, msgr2 };

</script>
</html>
