<script lang="ts">
    import {
        CheckIcon,
        ChevronRightIcon,
        CopyIcon,
        Loader2Icon,
        MessageSquareIcon,
        SendIcon,
        UnplugIcon,
        UserIcon,
    } from '@lucide/svelte';
    import {
        ConnectionState,
        type DeliverySuccessMessage,
        type Message,
        MessageType,
        type ReceiveHl7v2Message,
    } from '@hnu.de/hl7v2-shared';
    import { Button } from '$lib/components/ui/button';
    import { Card, CardContent, CardHeader } from '$lib/components/ui/card';
    import { Input } from '$lib/components/ui/input';
    import { Label } from '$lib/components/ui/dropdown-menu';
    import { env } from '$env/dynamic/public';
    import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '$lib/components/ui/tooltip';
    import { dev } from '$app/environment';
    import { toast } from 'svelte-sonner';

    // connection state
    let ws = $state<WebSocket | undefined>(undefined); // websocket client
    let stationId = $state<string | undefined>(undefined); // stationId assigned by server
    let connectionState = $state<ConnectionState>(ConnectionState.disconnected);

    // client state
    let composedMessage = $state(''); // content of text-box
    let sentMessages = $state<DeliverySuccessMessage[]>([]);
    let receivedMessages = $state<ReceiveHl7v2Message[]>([]);
    let isSending = $state(false);
    let copySuccess = $state(false);
    let deliveryError = $state('');

    // segment presets
    const segmentTemplates = {
        MSH: {
            name: 'Message Header',
            template: () => `MSH|^~\\&|SENDER_APP|SENDER_FACILITY|RECIPIENT_USER_ID|RECEIVER_FACILITY|${new Date().toISOString().replace(/[-:.]/g, '').slice(0, 14)}||ADT^A01|${Date.now()}|P|2.3`,
        },
        PID: {
            name: 'Patient Identification',
            template: () => `PID|||PATIENT_MRN||Doe^John^J||19900101|M`,
        },
        PV1: {
            name: 'Patient Visit',
            template: () => `PV1||I|ER^101^A|||1234^Welby^Marcus`,
        },
        AL1: {
            name: 'Patient Allergy Information',
            template: () => 'AL1|1|DA|12345^Penicillin|SV|Hives',
        },
        ITM: {
            name: 'Material Item',
            template: () => 'ITM|1|ITEM-789|Gauze Pads|Sterile Gauze Pads 4x4',
        },
        IVC: {
            name: 'Invoice',
            template: () => 'IVC|INV-987|ACCT-654|500.00|20230201',
        },
    };
    const segmentTypes = Object.keys(segmentTemplates) as Array<keyof typeof segmentTemplates>;

    // handles the connection process and message handling
    function connectToServer() {

        console.log('Connecting to server...');
        connectionState = ConnectionState.connecting;

        const socket = new WebSocket(`${dev ? 'ws' : 'wss'}://${env.PUBLIC_SERVER}`);

        // store websocket on successful connection
        socket.onopen = () => {
            console.log('WebSocket connection established.');
            ws = socket;
            isSending = false
        };

        // register message handlers
        socket.onmessage = (event) => {

            // parse every message as type Message
            const message = JSON.parse(event.data) as Message;
            console.log('Message received from server:', message);

            // react based on message type
            switch (message.type) {

                // initial message from server assigning ID
                case MessageType.assign_id:
                    stationId = message.payload.stationId;
                    composedMessage = segmentTemplates.MSH.template();
                    connectionState = ConnectionState.connected;
                    break;

                // message from another client
                case MessageType.receive_hl7v2:
                    receivedMessages = [message, ...receivedMessages];
                    break;

                // our message was successfully delivered
                case MessageType.delivery_success:
                    sentMessages = [message, ...sentMessages];
                    toast.success("Message delivered successfully")
                    isSending = false
                    break;

                // message from server due to delivery error
                case MessageType.delivery_error:
                    deliveryError = message.payload.error;
                    toast.error(deliveryError);
                    isSending = false
                    break;
            }
        };

        // reset connection state and websocket client on disconnect
        socket.onclose = () => {
            console.log('WebSocket connection closed.');
            connectionState = ConnectionState.disconnected;
            ws = undefined;
        };

        // reset connection state and websocket client on error
        socket.onerror = (error) => {
            console.error('WebSocket error:', error);
            connectionState = ConnectionState.disconnected;
            ws = undefined;
        };
    }

    // reset connection state and websocket client on tab closed
    $effect(() => {
        return () => {
            if (ws && ws.readyState === WebSocket.OPEN) {
                connectionState = ConnectionState.disconnected;
                ws.close();
            }
        };
    });

    // adds the template of the provided segment type at the bottom of the textbox
    function addSegment(type: keyof typeof segmentTemplates) {
        const template = segmentTemplates[type].template();
        composedMessage += `\r\n${template}`;
    }

    // handles sending a message to the websocket server
    function handleSendMessage(message: string) {

        // check for active connection
        if (!ws || ws.readyState !== WebSocket.OPEN || isSending || !stationId) {
            console.log('Socket not ready');
            return;
        }

        // set UI state
        isSending = true;
        deliveryError = '';

        // construct Message object and send it
        const messageToSend = {
            type: 'send_hl7v2',
            payload: { message },
        } as Message;
        ws.send(JSON.stringify(messageToSend));
    }

    // copies the stationId to the clipboard and handles UI state
    function copyStationId() {
        if (stationId) {
            navigator.clipboard.writeText(stationId).then(() => {
                copySuccess = true;
                setTimeout(() => copySuccess = false, 2000);
            });
        }
    }

</script>


<!-- 'component' for displaying a message in the message lists (sent and received) -->
{#snippet message(msg: ReceiveHl7v2Message)}
    <div class="bg-foreground/10 p-3 rounded-md space-y-1">
        <p class="text-xs">{new Date(msg.payload.timestamp).toLocaleString()}</p>
        <pre class="text-sm whitespace-pre-wrap break-all">{msg.payload.message}</pre>
    </div>
{/snippet}


<div class="p-4 lg:p-8">
    <div class="max-w-7xl mx-auto">
        <main class="grid grid-cols-1 lg:grid-cols-3 gap-6">

            <!-- Left Column: Station Info & Logs -->
            <div class="space-y-6">
                <Card>
                    <CardHeader class="flex flex-row items-center">
                        <UserIcon class="mr-2"/>
                        Your Info
                    </CardHeader>
                    <CardContent>
                        {#if connectionState === ConnectionState.connected && stationId}
                            <Label class="">Station ID</Label>
                            <div class="flex items-center space-x-2">
                                <Input disabled bind:value={stationId}/>
                                <Button variant="outline" size="icon" onclick={copyStationId}>
                                    {#if copySuccess}
                                        <CheckIcon class={'text-green-400'}/>
                                    {:else}
                                        <CopyIcon/>
                                    {/if}
                                </Button>
                            </div>
                        {:else if connectionState === ConnectionState.disconnected}
                            <div class="flex items-center space-x-2">
                                <UnplugIcon/>
                                <span>Disconnected</span>
                            </div>
                        {:else if connectionState === ConnectionState.connecting}
                            <div class="flex items-center space-x-2">
                                <Loader2Icon class="animate-spin"/>
                                <span>Connecting...</span>
                            </div>
                        {/if}
                    </CardContent>
                </Card>

                <Card>
                    <CardHeader class="flex items-center space-x-2">
                        <ChevronRightIcon class="text-green-400"/>
                        <span>Received Messages</span>
                    </CardHeader>
                    <CardContent class="space-y-2">
                        {#if receivedMessages.length === 0}
                            <p class="text-foreground/70 italic">Waiting for incoming messages...</p>
                        {/if}
                        {#each receivedMessages as msg (msg.payload.timestamp)}
                            {@render message(msg)}
                        {/each}
                    </CardContent>
                </Card>
            </div>

            <!-- Right Column: Composer and Sent Log -->
            <div class="lg:col-span-2 space-y-6">
                <Card>
                    <CardHeader class="flex items-center space-x-2">
                        <MessageSquareIcon/>
                        <span>HL7v2 Message Editor</span>
                    </CardHeader>
                    <CardContent>
                        {#if connectionState === ConnectionState.connected && stationId}
                            <div>
                                <div class="flex flex-wrap gap-2 mb-4">
                                    {#each segmentTypes as type (type)}
                                        {#if type !== 'MSH'}
                                            <TooltipProvider>
                                                <Tooltip>
                                                    <TooltipTrigger>
                                                        <Button onclick={() => addSegment(type)}>
                                                            + {type}
                                                        </Button>
                                                    </TooltipTrigger
                                                    >
                                                    <TooltipContent>
                                                        {segmentTemplates[type].name}
                                                    </TooltipContent>
                                                </Tooltip>
                                            </TooltipProvider>
                                        {/if}
                                    {/each}
                                </div>
                                <textarea bind:value={composedMessage}
                                          class="w-full h-64 bg-black font-mono text-sm text-green-400 p-4 rounded-md"></textarea>
                                <Button onclick={() => handleSendMessage(composedMessage)}
                                        disabled={isSending || !composedMessage} class="w-full space-x-2">
                                    <SendIcon/>
                                    <span>Send Message</span>
                                </Button>
                            </div>
                        {:else if connectionState === ConnectionState.disconnected}
                            <div class="flex flex-col items-center justify-center min-h-96 text-foreground/70 space-y-4">
                                <h3 class="text-xl font-bold">Connect as a client</h3>
                                <Button onclick={connectToServer}>Connect</Button>
                            </div>
                        {:else if connectionState === ConnectionState.connecting}
                            <div class="flex flex-col items-center justify-center min-h-96 text-foreground/70">
                                <Loader2Icon class="animate-spin mb-4" size={48}/>
                                <h3 class="text-xl font-bold">Connecting...</h3>
                            </div>
                        {/if}
                    </CardContent>
                </Card>

                <Card>
                    <CardHeader class="flex items-center space-x-2">
                        <SendIcon class="text-blue-400"/>
                        <span>Sent Messages Log</span>
                    </CardHeader>
                    <CardContent class="space-y-2">
                        {#if sentMessages.length === 0}
                            <p class="text-foreground/70 italic">Your sent messages will appear here.</p>
                        {/if}
                        {#each sentMessages as msg (msg.payload.timestamp)}
                            {@render message(msg)}
                        {/each}
                    </CardContent>
                </Card>
            </div>
        </main>
    </div>
</div>
