<script lang="ts">
	interface Props {
		log_lvl?: number;
		ws_connect?: boolean; // If true then we should be trying to connect to the WS server.
		ws_connect_status?: null | string;
		ws_server?: string;
		ws_retry_delay?: number;
		ws_retry_count?: number;
		base_url?: any;
		group_id?: string;
		client_id?: any;
		cmd?: null | string;
		msg?: null | string;
		type?: null | string; // msg, cmd, json, hello, bye
		trigger_send?: any;
		trigger_connect?: boolean;
		trigger_disconnect?: boolean;
		classes?: string;
		hide__ws_element?: boolean;
		hide__ws_form?: boolean;
		hide__ws_messages?: boolean;
		hide__ws_commands?: boolean;

		ws_conn_status?: any;
		ws_recv_status?: any;
		ws_sent_status?: any;
	}

	let {
		log_lvl = 0,
		ws_connect = $bindable(false),
		ws_connect_status = $bindable(null),
		ws_server = 'dev-api.oneskyit.com',
		ws_retry_delay = 3500,
		ws_retry_count = 0,
		base_url = `wss://${ws_server}/ws`,
		group_id = $bindable('ae-grp-99'),
		client_id = $bindable(Date.now()),
		cmd = $bindable(null),
		msg = $bindable(null),
		type = null,
		trigger_send = $bindable(null),
		trigger_connect = $bindable(false),
		trigger_disconnect = $bindable(false),
		classes = 'container p-1 bg-pink-100 text-xs mx-auto pb-16 mb-20 sm:mb-12 md:mb-8',
		hide__ws_element = $bindable(false),
		hide__ws_form = $bindable(true),
		hide__ws_messages = $bindable(false),
		hide__ws_commands = $bindable(false),

		ws_conn_status = $bindable(null),
		ws_recv_status = $bindable(null),
		ws_sent_status = $bindable(null)
	}: Props = $props();

	// import { run, preventDefault } from 'svelte/legacy';
	// import { createEventDispatcher, onMount } from 'svelte';

	// *** Set initial variables
	// const dispatch = createEventDispatcher();

	// JSON formatted data
	let ws_data = $state({
		client_id: null, // The device or browser ID if available.
		// 'src': null, // Sending client
		// 'account_id': null, // Essentially the person ID or user ID if available.
		// 'dest': null, // Destination client
		target: 'echo', // echo, dm (direct), grp (group), all (broadcast)
		type: 'cmd', // msg, cmd, json, hello, bye
		// 'grp': null, // Destination group
		msg: null, // Message string
		cmd: null // Command string
		// 'data': null,
		// 'b64': null,
	});

	let ws_received_list_cmd: string[] = $state([]);
	let ws_received_list_other: any[] = $state([]);
	let ws_received_list_msg: string[] = [];

	// onMount(async () => {
	//     console.log('** Component Mounted: ** Element Websocket v2');
	// });

	// *** Functions and Logic

	function ws_connect_group_id({ group_id, client_id }) {
		if (!group_id) {
			group_id = 'ae-grp-99';
			console.log(`WS: No group_id specified! Setting to default: ${group_id}`);
			return false;
		}
		if (!client_id) {
			client_id = Date.now();
			console.log(`WS: No client_id specified! Setting to current timestamp: ${client_id}`);
			// return false;
		}
		console.log(`WS Connect URL: ${base_url}/group/${group_id}/client/${client_id}`);
		let ws_connection = new WebSocket(`${base_url}/group/${group_id}/client/${client_id}`);

		ws_connection.onopen = function () {
			console.log('WS: connected');

			ws_connect_status = 'connected';

			// dispatch('ws_conn', {
			//     'status': 'connected'
			// });
			ws_conn_status = 'connected';

			ws_retry_count = 0;

			// ws_connection.send(JSON.stringify({
			//     client_id: client_id,
			//     target: 'echo',
			//     type: 'hello',
			//     group_id: group_id,
			//     msg: 'You are connected!'
			// }));

			ws_connection.send(
				JSON.stringify({
					client_id: client_id,
					target: 'all',
					type: 'hello',
					group_id: group_id,
					msg: `Client ${client_id.toString().slice(-5)} connected!`
				})
			);
		};

		ws_connection.onmessage = function (event) {
			if (log_lvl) {
				console.log('WS: message received', event);
			}

			let ws_recv_data = JSON.parse(event.data);
			if (log_lvl) {
				console.log('WS: Received data:', ws_recv_data);
			}

			if (client_id == ws_recv_data.client_id) {
				if (log_lvl) {
					console.log('WS: Message received was sent by self and is an echo that can be ignored.');
				}
				return false;
			}

			if (ws_recv_data.type == 'cmd') {
				console.log(`WS: Type CMD: ${ws_recv_data.cmd}`);
				ws_received_list_cmd.unshift(ws_recv_data); // Add to the beginning of the list
				// ws_received_list_cmd.push(ws_recv_data); // Add to the end of the list
				ws_received_list_cmd = ws_received_list_cmd; // trigger Svelte update -2024-10-04
			} else {
				console.log('WS: Type other');
				ws_received_list_other.unshift(ws_recv_data); // Add to the beginning of the list
				// ws_received_list_other.push(ws_recv_data); // Add to the end of the list
				ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04
			}

			// dispatch('ws_recv', {
			//     // 'client_id': ws_data.client_id, // The device or browser ID if available.
			//     'src': ws_recv_data.client_id, // The device or browser ID if available.
			//     // 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
			//     'dest': group_id, // Destination client
			//     'target': ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
			//     'type': ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
			//     // 'grp': ws_recv_data.grp, // Destination group
			//     'msg': ws_recv_data.msg, // Message string
			//     'cmd': ws_recv_data.cmd, // Command string
			// });
			ws_recv_status = {
				// 'client_id': ws_data.client_id, // The device or browser ID if available.
				src: ws_recv_data.client_id, // The device or browser ID if available.
				// 'account_id': ws_recv_data.account_id, // Essentially the person ID or user ID if available.
				dest: group_id, // Destination client
				target: ws_recv_data.target, // echo, dm (direct), grp (group), all (broadcast)
				type: ws_recv_data.type, // Message type (msg, cmd, json, hello, bye)
				// 'grp': ws_recv_data.grp, // Destination group
				msg: ws_recv_data.msg, // Message string
				cmd: ws_recv_data.cmd // Command string
			};
		};

		ws_connection.onclose = function (event) {
			console.log('WS: connection closed');

			ws_connection.send(
				JSON.stringify({
					client_id: client_id,
					target: 'all',
					type: 'hello',
					group_id: group_id,
					msg: `Client ${client_id} is disconnecting!`
				})
			);

			ws_connect_status = 'disconnected';

			let fake_ws_recv_data = {
				client_id: client_id,
				target: 'local',
				type: 'bye',
				group_id: group_id,
				msg: `LOCAL Client ${client_id} has disconnected!`
			};
			ws_received_list_other.unshift(fake_ws_recv_data);
			ws_received_list_other = ws_received_list_other; // trigger Svelte update -2024-10-04

			// dispatch('ws_conn', {
			//     'status': 'disconnected'
			// });
			ws_conn_status = 'disconnected';

			if (ws_connect) {
				if (ws_retry_count >= 10) {
					ws_retry_delay = ws_retry_delay += 4999; // Set to a very long time
					ws_retry_delay = Math.min(ws_retry_delay, 120000); // Max of 2 minutes
					console.log(
						`WS: Retry count exceeded. Increasing the delay. ws_retry_count=${ws_retry_count} ws_retry_delay=${ws_retry_delay}`
					);
				}
				setTimeout(function () {
					console.log('WS: Disconnected... Try again!');
					ws_retry_count += 1;
					ws_connect_group_id({ group_id: group_id, client_id: client_id });
					console.log('WS: Again done?');
				}, ws_retry_delay);
			}
		};

		ws_connection.onerror = function (event) {
			console.log('WS: connection error???');

			ws_connection.send(
				JSON.stringify({
					client_id: client_id,
					target: 'all',
					type: 'hello',
					group_id: group_id,
					msg: `Client ${client_id} is having trouble?!`
				})
			);
		};

		// NOTE WARNING: Uncommenting this seems to break FastAPI somehow???
		// NOTE: from FastAPI log: RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close'.
		// ws_connection.onerror = function(err) {
		//     console.error('WS socket error: ', err.message, 'Closing socket');
		//     // ws_connection.close();
		// };

		return ws_connection;
	}

	// Start the WS function
	let ws_group: any = $state(null);

	function handle_send_ws_data() {
		console.log(ws_data);
		if (!ws_data) {
			return false;
		}
		if (!ws_group) {
			console.log('WS: No connection!');
			return false;
		}
		let ws_data_json_str = JSON.stringify(ws_data);
		let resp = ws_group.send(ws_data_json_str);
		console.log(`WS: Send data response:`, resp);

		// dispatch('ws_sent', {
		//     // 'client_id': ws_data.client_id, // The device or browser ID if available.
		//     'src': ws_data.client_id, // The device or browser ID if available.
		//     // 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
		//     'dest': group_id, // Destination client
		//     'group_id': group_id,
		//     'target': ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
		//     'type': ws_data.type, // Message type (msg, cmd, json, hello, bye)
		//     // 'grp': ws_data.grp, // Destination group
		//     'msg': ws_data.msg, // Message string
		//     'cmd': ws_data.cmd, // Command string
		// });
		ws_sent_status = {
			// 'client_id': ws_data.client_id, // The device or browser ID if available.
			src: ws_data.client_id, // The device or browser ID if available.
			// 'account_id': ws_data.account_id, // Essentially the person ID or user ID if available.
			dest: group_id, // Destination client
			group_id: group_id,
			target: ws_data.target, // echo, dm (direct), grp (group), all (broadcast)
			type: ws_data.type, // Message type (msg, cmd, json, hello, bye)
			// 'grp': ws_data.grp, // Destination group
			msg: ws_data.msg, // Message string
			cmd: ws_data.cmd // Command string
		};

		cmd = '';
		msg = '';
	}

	$effect(() => {
		if (ws_connect && group_id) {
			console.log('HERE!!!!!');
			ws_group = ws_connect_group_id({ group_id: group_id, client_id: client_id });
			// } else if (!ws_connect) {
			//     console.log('HERE!!!!!');
			//     log_lvl = 1;
			//     if (log_lvl) {
			//         console.log(`WS: WS not set to connect. Need to close WS Group connection.`);
			//     }
			//     ws_group?.close();
			//     ws_connect_status = 'disconnected';
		} else {
			console.log('HERE!!!!!');
			log_lvl = 1;
			if (log_lvl) {
				console.log(`WS: Not connecting. ws_connect=${ws_connect} group_id=${group_id}`);
			}
			ws_group?.close();
			ws_connect_status = 'disconnected';
		}
	});

	$effect(() => {
		if (trigger_send && cmd) {
			trigger_send = null;
			console.log('WS: Send triggered!');
			console.log(cmd);
			ws_data.target = 'group';
			ws_data.type = 'cmd';
			ws_data.cmd = cmd;
			handle_send_ws_data();
			cmd = '';
		}
	});

	$effect(() => {
		if (trigger_connect) {
			trigger_connect = false;
			if (!ws_connect) {
				ws_connect = true;
			}
			console.log('WS: Connect triggered!');
		}
	});

	$effect(() => {
		if (trigger_disconnect) {
			console.log('WS: Disconnect triggered!');
			trigger_disconnect = false;
			if (ws_connect) {
				ws_connect = false;
			}
			if (ws_group) {
				ws_group.close();
				ws_group = null;
				ws_connect_status = 'disconnected';
			}
		}
	});

	function preventDefault<T extends Event>(fn: (event: T) => void) {
		return function (event: T) {
			event.preventDefault();
			fn(event);
		};
	}
</script>

<section
	class:hidden={!ws_connect || hide__ws_element}
	class="ae_element__websocket container p-1 bg-pink-100 text-xs mx-auto pb-16 mt-32 mb-32 relative"
>
	<span class="absolute top-0 right-0 flex flex-col gap-1">
		<button
			type="button"
			onclick={() => {
				hide__ws_form = !hide__ws_form;
			}}
			class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
			class:preset-tonal-tertiary={hide__ws_form}
			class:preset-filled-tertiary-500={!hide__ws_form}
		>
			{#if hide__ws_form}
				Show Form
			{:else}
				Hide Form?
			{/if}
		</button>
		<button
			type="button"
			onclick={() => {
				hide__ws_messages = !hide__ws_messages;
			}}
			class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
			class:preset-tonal-tertiary={hide__ws_messages}
			class:preset-filled-tertiary-500={!hide__ws_messages}
		>
			{#if hide__ws_messages}
				Show Messages
			{:else}
				Hide Messages?
			{/if}
		</button>
		<button
			type="button"
			onclick={() => {
				hide__ws_commands = !hide__ws_commands;
			}}
			class="btn btn-sm text-xs hover:preset-filled-tertiary-500"
			class:preset-tonal-tertiary={hide__ws_commands}
			class:preset-filled-tertiary-500={!hide__ws_commands}
		>
			{#if hide__ws_commands}
				Show Commands
			{:else}
				Hide Commands?
			{/if}
		</button>
	</span>

	<header>
		<h1 class="h6 text-center">Websocket Messages & Commands</h1>
	</header>

	<!-- <form on:submit|preventDefault={handle_send_message}>
    <select bind:value={type}>
        <option value="">None</option>
        <option value="echo">Echo</option>
        <option value="dm">Direct Message</option>
        <option value="group">Group Message</option>
        <option value="all">Broadcast to All</option>
        <option value="cmd">Command</option>
    </select>

    <select bind:value={group_id}>
        <option value="">None</option>
        <option value="test_grp_123">123</option>
        <option value="test_grp_999">999</option>
        <option value="test_grp_poster">A Poster Group</option>
    </select>

    <input type="text" bind:value={message_text} placeholder="Your message"/>

    <button>Send</button>
</form> -->

	{#if !hide__ws_form}
		<form onsubmit={preventDefault(handle_send_ws_data)}>
			<select bind:value={ws_data.type} class="input text-sm w-24">
				<option value="">None</option>
				<option value="echo">Echo</option>
				<option value="dm">Direct Message</option>
				<option value="group">Group Message</option>
				<option value="all">Broadcast to All</option>
				<option value="cmd">Command</option>
			</select>
			<input type="text" bind:value={group_id} placeholder="Group ID" class="input text-sm w-36" />
			<!-- <select bind:value={group_id}>
        <option value="">None</option>
        <option value="test_grp_123">123</option>
        <option value="test_grp_999">999</option>
        <option value="test_grp_poster">A Poster Group</option>
    </select> -->

			<!-- <input type="text" bind:value={dm_client_id} placeholder="Direct message client ID"/> -->

			<input
				type="text"
				bind:value={ws_data.cmd}
				placeholder="Your command"
				class="input text-sm w-36"
			/>
			<input
				type="text"
				bind:value={ws_data.msg}
				placeholder="Your message"
				class="input text-xs w-96"
			/>

			<button type="submit" class="btn btn-sm preset-tonal-warning">Send Group</button>
		</form>
	{/if}

	<!-- <pre>
ae_load:event_session=jEG9APQRUs8 (Poster Session #3: Work Never Ends - Pythagoras)
ae_open:event_file=CHqU5sW7xbc (jpg)
ae_open:event_file=Kljq0uiTlXt (video)
</pre> -->

	<!-- <hr> -->

	<section class:hidden={hide__ws_messages}>
		<h2 class="text-center underline">Messages [grp, client, target, type]</h2>

		<ol class="list-decimal list-outside max-h-24 overflow-y-auto messages">
			{#each ws_received_list_other as msg_entry, index}
				<li class="ml-4">
					<div class="flex flex-row justify-between gap-1 w-full">
						<span>
							[{msg_entry.group_id || 'No Group ID'}]
							{msg_entry.client_id.toString().slice(-5) || 'No Client ID'}
							–
							{msg_entry.target || 'No Target'}
							|
							<!-- &ndash; -->
							{msg_entry.type || 'No Type'}:
						</span>
						<span class="justify-self-end">
							"{msg_entry.msg}"
						</span>
						<!-- <br>{JSON.stringify(msg_entry)} -->
					</div>
				</li>
			{/each}
		</ol>
	</section>
	<!-- End Messages -->

	<hr />

	<section class:hidden={hide__ws_commands}>
		<h2 class="text-center underline">Commands</h2>

		<ol class="list-decimal list-outside max-h-24 overflow-y-auto commands">
			{#each ws_received_list_cmd as cmd_entry}
				<li class="ml-4">
					<div class="flex flex-row justify-between gap-1 w-full">
						<span>
							[{cmd_entry.group_id || 'No Group ID'}]
							{cmd_entry.client_id.toString().slice(-5) || 'No Client ID'}
							—
							{cmd_entry.target || 'No Target'}
							|
							<!-- &mdash; -->
							{cmd_entry.type || 'No Type'}:
						</span>
						<span class="justify-self-end">
							"{cmd_entry.cmd}"
						</span>
					</div>
				</li>
			{/each}
		</ol>
	</section>
	<!-- End Commands -->
</section>

<style>
	/* .websocket_element {
    background-color: white;
    border-top: dashed medium gray;
    color: gray;
    font-size: .7em;
}
.websocket_element header {
    font-size: .7em;
}
.websocket_element h2 {
    font-size: .9em;
} */
</style>
