<!-- [DEF:TaskRunner:Component] -->
<!--
@SEMANTICS: task, runner, logs, websocket
@PURPOSE: Connects to a WebSocket to display real-time logs for a running task.
@LAYER: UI
@RELATION: DEPENDS_ON -> frontend/src/lib/stores.js

@PROPS: None
@EVENTS: None
-->
<script>
    // [SECTION: IMPORTS]
    import { onMount, onDestroy } from 'svelte';
    import { get } from 'svelte/store';
    import { selectedTask, taskLogs } from '../lib/stores.js';
    import { getWsUrl } from '../lib/api.js';
    import { addToast } from '../lib/toasts.js';
    import MissingMappingModal from './MissingMappingModal.svelte';
    // [/SECTION]

    let ws;
    let reconnectAttempts = 0;
    let maxReconnectAttempts = 10;
    let initialReconnectDelay = 1000;
    let maxReconnectDelay = 30000;
    let reconnectTimeout;
    let waitingForData = false;
    let dataTimeout;
    let connectionStatus = 'disconnected'; // 'connecting', 'connected', 'disconnected', 'waiting', 'completed', 'awaiting_mapping'
    let showMappingModal = false;
    let missingDbInfo = { name: '', uuid: '' };
    let targetDatabases = [];

    // [DEF:connect:Function]
    /**
     * @purpose Establishes WebSocket connection with exponential backoff.
     */
    function connect() {
        const task = get(selectedTask);
        if (!task || connectionStatus === 'completed') return;

        console.log(`[TaskRunner][Entry] Connecting to logs for task: ${task.id} (Attempt ${reconnectAttempts + 1})`);
        connectionStatus = 'connecting';
        
        const wsUrl = getWsUrl(task.id);
        ws = new WebSocket(wsUrl);

        ws.onopen = () => {
            console.log('[TaskRunner][Coherence:OK] WebSocket connection established');
            connectionStatus = 'connected';
            reconnectAttempts = 0;
            startDataTimeout();
        };

        ws.onmessage = (event) => {
            const logEntry = JSON.parse(event.data);
            taskLogs.update(logs => [...logs, logEntry]);
            resetDataTimeout();
            
            // Check for completion message (if backend sends one)
            if (logEntry.message && logEntry.message.includes('Task completed successfully')) {
                connectionStatus = 'completed';
                ws.close();
            }

            // Check for missing mapping signal
            if (logEntry.message && logEntry.message.includes('Missing mapping for database UUID')) {
                const uuidMatch = logEntry.message.match(/UUID: ([\w-]+)/);
                if (uuidMatch) {
                    missingDbInfo = { name: 'Unknown', uuid: uuidMatch[1] };
                    connectionStatus = 'awaiting_mapping';
                    fetchTargetDatabases();
                    showMappingModal = true;
                }
            }
        };

        ws.onerror = (error) => {
            console.error('[TaskRunner][Coherence:Failed] WebSocket error:', error);
            connectionStatus = 'disconnected';
        };

        ws.onclose = (event) => {
            console.log(`[TaskRunner][Exit] WebSocket connection closed (Code: ${event.code})`);
            clearTimeout(dataTimeout);
            waitingForData = false;

            if (connectionStatus !== 'completed' && reconnectAttempts < maxReconnectAttempts) {
                const delay = Math.min(initialReconnectDelay * Math.pow(2, reconnectAttempts), maxReconnectDelay);
                console.log(`[TaskRunner][Action] Reconnecting in ${delay}ms...`);
                reconnectTimeout = setTimeout(() => {
                    reconnectAttempts++;
                    connect();
                }, delay);
            } else if (reconnectAttempts >= maxReconnectAttempts) {
                console.error('[TaskRunner][Coherence:Failed] Max reconnect attempts reached.');
                addToast('Failed to connect to log stream after multiple attempts.', 'error');
            }
        };
    }
    // [/DEF:connect]

    async function fetchTargetDatabases() {
        const task = get(selectedTask);
        if (!task || !task.params.to_env) return;
        
        try {
            // We need to find the environment ID by name first
            const envsRes = await fetch('/api/environments');
            const envs = await envsRes.json();
            const targetEnv = envs.find(e => e.name === task.params.to_env);
            
            if (targetEnv) {
                const res = await fetch(`/api/environments/${targetEnv.id}/databases`);
                targetDatabases = await res.json();
            }
        } catch (e) {
            console.error('Failed to fetch target databases', e);
        }
    }

    async function handleMappingResolve(event) {
        const task = get(selectedTask);
        const { sourceDbUuid, targetDbUuid, targetDbName } = event.detail;
        
        try {
            // 1. Save mapping to backend
            const envsRes = await fetch('/api/environments');
            const envs = await envsRes.json();
            const srcEnv = envs.find(e => e.name === task.params.from_env);
            const tgtEnv = envs.find(e => e.name === task.params.to_env);

            await fetch('/api/mappings', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    source_env_id: srcEnv.id,
                    target_env_id: tgtEnv.id,
                    source_db_uuid: sourceDbUuid,
                    target_db_uuid: targetDbUuid,
                    source_db_name: missingDbInfo.name,
                    target_db_name: targetDbName
                })
            });

            // 2. Resolve task
            await fetch(`/api/tasks/${task.id}/resolve`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    resolution_params: { resolved_mapping: { [sourceDbUuid]: targetDbUuid } }
                })
            });

            connectionStatus = 'connected';
            addToast('Mapping resolved, migration continuing...', 'success');
        } catch (e) {
            addToast('Failed to resolve mapping: ' + e.message, 'error');
        }
    }

    function startDataTimeout() {
        waitingForData = false;
        dataTimeout = setTimeout(() => {
            if (connectionStatus === 'connected') {
                waitingForData = true;
            }
        }, 5000);
    }

    function resetDataTimeout() {
        clearTimeout(dataTimeout);
        waitingForData = false;
        startDataTimeout();
    }

    // [DEF:onMount:Function]
    onMount(() => {
        // Subscribe to selectedTask changes
        const unsubscribe = selectedTask.subscribe(task => {
            if (task) {
                console.log(`[TaskRunner][Action] Task selected: ${task.id}. Initializing connection.`);
                if (ws) ws.close();
                clearTimeout(reconnectTimeout);
                reconnectAttempts = 0;
                connectionStatus = 'disconnected';
                taskLogs.set([]);
                connect();
            }
        });
        return unsubscribe;
    });
    // [/DEF:onMount]

    // [DEF:onDestroy:Function]
    /**
     * @purpose Close WebSocket connection when the component is destroyed.
     */
    onDestroy(() => {
        clearTimeout(reconnectTimeout);
        clearTimeout(dataTimeout);
        if (ws) {
            console.log("[TaskRunner][Action] Closing WebSocket connection.");
            ws.close();
        }
    });
    // [/DEF:onDestroy]
</script>

<!-- [SECTION: TEMPLATE] -->
<div class="p-4 border rounded-lg bg-white shadow-md">
    {#if $selectedTask}
        <div class="flex justify-between items-center mb-2">
            <h2 class="text-xl font-semibold">Task: {$selectedTask.plugin_id}</h2>
            <div class="flex items-center space-x-2">
                {#if connectionStatus === 'connecting'}
                    <span class="flex h-3 w-3 relative">
                        <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
                        <span class="relative inline-flex rounded-full h-3 w-3 bg-yellow-500"></span>
                    </span>
                    <span class="text-xs text-gray-500">Connecting...</span>
                {:else if connectionStatus === 'connected'}
                    <span class="h-3 w-3 rounded-full bg-green-500"></span>
                    <span class="text-xs text-gray-500">Live</span>
                {:else if connectionStatus === 'completed'}
                    <span class="h-3 w-3 rounded-full bg-blue-500"></span>
                    <span class="text-xs text-gray-500">Completed</span>
                {:else if connectionStatus === 'awaiting_mapping'}
                    <span class="h-3 w-3 rounded-full bg-orange-500 animate-pulse"></span>
                    <span class="text-xs text-gray-500">Awaiting Mapping</span>
                {:else}
                    <span class="h-3 w-3 rounded-full bg-red-500"></span>
                    <span class="text-xs text-gray-500">Disconnected</span>
                {/if}
            </div>
        </div>

        <div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto relative">
            {#each $taskLogs as log}
                <div>
                    <span class="text-gray-400">{new Date(log.timestamp).toLocaleTimeString()}</span>
                    <span class="{log.level === 'ERROR' ? 'text-red-500' : 'text-green-400'}">[{log.level}]</span>
                    <span>{log.message}</span>
                </div>
            {/each}
            
            {#if waitingForData}
                <div class="text-gray-500 italic mt-2 animate-pulse">
                    Waiting for data...
                </div>
            {/if}
        </div>
    {:else}
        <p>No task selected.</p>
    {/if}
</div>

<MissingMappingModal
    bind:show={showMappingModal}
    sourceDbName={missingDbInfo.name}
    sourceDbUuid={missingDbInfo.uuid}
    {targetDatabases}
    on:resolve={handleMappingResolve}
    on:cancel={() => { connectionStatus = 'disconnected'; ws.close(); }}
/>
<!-- [/SECTION] -->

<!-- [/DEF:TaskRunner] -->
