<!-- #region TaskRunner [C:3] [TYPE Component] [SEMANTICS task, runner, websocket, realtime, log] -->
<!-- @BRIEF Component component: components/TaskRunner.svelte -->
<!-- @LAYER UI -->
<!--
@UX_STATE: Loading -> Default

@SEMANTICS: task, runner, logs, websocket
@PURPOSE: Connects to a WebSocket to display real-time logs for a running task with filtering support.
@LAYER UI
@RELATION DEPENDS_ON -> [TaskLogPanel]
-->
<script lang="ts">
    // [SECTION: IMPORTS]
    import { onMount, onDestroy } from 'svelte';
    import { selectedTask, taskLogs } from '$lib/stores.svelte.js';
    import { getWsUrl, api } from '../lib/api.js';
    import { addToast } from '$lib/toasts.svelte.js';
    import MissingMappingModal from './MissingMappingModal.svelte';
    import PasswordPrompt from './PasswordPrompt.svelte';
    import TaskLogPanel from './tasks/TaskLogPanel.svelte';
    import { t } from '$lib/i18n/index.svelte.js';
    // [/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', 'awaiting_input'
    let showMappingModal = false;
    let missingDbInfo = { name: '', uuid: '' };
    let targetDatabases = [];
    
    let showPasswordPrompt = false;
    let passwordPromptData = { databases: [], errorMessage: '' };

    let selectedSource = 'all';
    let selectedLevel = 'all';

    // #region connect:Function [TYPE Function]
    /**
     * @purpose Establishes WebSocket connection with exponential backoff and filter parameters.
     * @pre selectedTask must be set in the store.
     * @post WebSocket instance created and listeners attached.
     */
    function connect() {
        const task = selectedTask.current;
        if (!task || connectionStatus === 'completed') return;

        console.log(`[EXT:frontend:TaskRunner][Entry] Connecting to logs for task: ${task.id} (Attempt ${reconnectAttempts + 1}) filters: source=${selectedSource}, level=${selectedLevel}`);
        connectionStatus = 'connecting';
        
        let wsUrl = getWsUrl(task.id);
        
        // Append filter parameters to WebSocket URL
        const params = new URLSearchParams();
        if (selectedSource !== 'all') params.append('source', selectedSource);
        if (selectedLevel !== 'all') params.append('level', selectedLevel);
        
        const queryString = params.toString();
        if (queryString) {
            wsUrl += (wsUrl.includes('?') ? '&' : '?') + queryString;
        }

        ws = new WebSocket(wsUrl);

        ws.onopen = () => {
            console.log('[EXT:frontend: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;
                }
            }
            
            // Check for password request via log context or message
            if (logEntry.message && logEntry.message.includes('Task paused for user input') && logEntry.context && logEntry.context.input_request) {
                const request = logEntry.context.input_request;
                if (request.type === 'database_password') {
                    connectionStatus = 'awaiting_input';
                    passwordPromptData = {
                        databases: request.databases || [],
                        errorMessage: request.error_message || ''
                    };
                    showPasswordPrompt = true;
                }
            }
        };

        if (task && task.status === 'AWAITING_INPUT' && task.input_request && task.input_request.type === 'database_password') {
             connectionStatus = 'awaiting_input';
             passwordPromptData = {
                 databases: task.input_request.databases || [],
                 errorMessage: task.input_request.error_message || ''
             };
             showPasswordPrompt = true;
        }
        
        ws.onerror = (error) => {
            console.error('[EXT:frontend:TaskRunner][Coherence:Failed] WebSocket error:', error);
            connectionStatus = 'disconnected';
        };

        ws.onclose = (event) => {
            console.log(`[EXT:frontend: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(`[EXT:frontend:TaskRunner][Action] Reconnecting in ${delay}ms...`);
                reconnectTimeout = setTimeout(() => {
                    reconnectAttempts++;
                    connect();
                }, delay);
            } else if (reconnectAttempts >= maxReconnectAttempts) {
                console.error('[EXT:frontend:TaskRunner][Coherence:Failed] Max reconnect attempts reached.');
                addToast($t.tasks?.log_stream_failed, 'error');
            }
        };
    }
    // #endregion connect:Function

    // #region handleFilterChange:Function [TYPE Function]
    /**
     * @purpose Handles filter changes and reconnects WebSocket with new parameters.
     * @pre event.detail contains source and level filter values.
     * @post WebSocket reconnected with new filter parameters, logs cleared.
     */
    function handleFilterChange(event) {
        const { source, level } = event.detail;
        if (selectedSource === source && selectedLevel === level) return;
        
        selectedSource = source;
        selectedLevel = level;
        
        console.log(`[EXT:frontend:TaskRunner] Filter changed, reconnecting WebSocket: source=${source}, level=${level}`);
        
        // Clear current logs when filter changes to avoid confusion
        taskLogs.set([]);
        
        if (ws) {
            ws.close(); // This will trigger reconnection via onclose if not completed
        } else {
            connect();
        }
    }
    // #endregion handleFilterChange:Function

    // #region fetchTargetDatabases:Function [TYPE Function]
    /**
     * @purpose Fetches available databases from target environment for mapping.
     * @pre selectedTask must have to_env parameter set.
     * @post targetDatabases array populated with available databases.
     */
    async function fetchTargetDatabases() {
        const task = selectedTask.current;
        if (!task || !task.params.to_env) return;
        
        try {
            const envs = await api.fetchApi('/environments');
            const targetEnv = envs.find(e => e.name === task.params.to_env);
            
            if (targetEnv) {
                targetDatabases = await api.fetchApi(`/environments/${targetEnv.id}/databases`);
            }
        } catch (e) {
            console.error('Failed to fetch target databases', e);
        }
    }
    // #endregion fetchTargetDatabases:Function

    // #region handleMappingResolve:Function [TYPE Function]
    /**
     * @purpose Resolves missing database mapping and continues migration.
     * @pre event.detail contains sourceDbUuid, targetDbUuid, targetDbName.
     * @post Mapping created in backend, task resumed with resolution params.
     */
    async function handleMappingResolve(event) {
        const task = selectedTask.current;
        const { sourceDbUuid, targetDbUuid, targetDbName } = event.detail;
        
        try {
            const envs = await api.fetchApi('/environments');
            const srcEnv = envs.find(e => e.name === task.params.from_env);
            const tgtEnv = envs.find(e => e.name === task.params.to_env);

            await api.postApi('/mappings', {
                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
            });

            await api.postApi(`/tasks/${task.id}/resolve`, {
                resolution_params: { resolved_mapping: { [sourceDbUuid]: targetDbUuid } }
            });

            connectionStatus = 'connected';
            addToast($t.tasks?.mapping_resolved, 'success');
        } catch (e) {
            addToast($t.tasks?.mapping_resolve_failed?.replace('{error}', e.message), 'error');
        }
    }
    // #endregion handleMappingResolve:Function
    
    // #region handlePasswordResume:Function [TYPE Function]
    /**
     * @purpose Submits passwords and resumes paused migration task.
     * @pre event.detail contains passwords object.
     * @post Task resumed with passwords, connection status restored to connected.
     */
    async function handlePasswordResume(event) {
        const task = selectedTask.current;
        const { passwords } = event.detail;
        
        try {
            await api.postApi(`/tasks/${task.id}/resume`, { passwords });
            
            showPasswordPrompt = false;
            connectionStatus = 'connected';
            addToast($t.tasks?.passwords_submitted, 'success');
        } catch (e) {
            addToast($t.tasks?.resume_failed?.replace('{error}', e.message), 'error');
        }
    }
    // #endregion handlePasswordResume:Function

    // #region startDataTimeout:Function [TYPE Function]
    /**
     * @purpose Starts timeout timer to detect idle connection.
     * @pre connectionStatus is 'connected'.
     * @post waitingForData set to true after 5 seconds if no data received.
     */
    function startDataTimeout() {
        waitingForData = false;
        dataTimeout = setTimeout(() => {
            if (connectionStatus === 'connected') {
                waitingForData = true;
            }
        }, 5000);
    }
    // #endregion startDataTimeout:Function

    // #region resetDataTimeout:Function [TYPE Function]
    /**
     * @purpose Resets data timeout timer when new data arrives.
     * @pre dataTimeout must be set.
     * @post waitingForData reset to false, new timeout started.
     */
    function resetDataTimeout() {
        clearTimeout(dataTimeout);
        waitingForData = false;
        startDataTimeout();
    }
    // #endregion resetDataTimeout:Function

    // #region onMount:Function [TYPE Function]
    /**
     * @purpose Initializes WebSocket connection when component mounts.
     * @pre Component must be mounted in DOM.
     * @post WebSocket connection established, subscription to selectedTask active.
     */
    onMount(() => {
        const unsubscribe = selectedTask.subscribe(task => {
            if (task) {
                console.log(`[EXT:frontend:TaskRunner][Action] Task selected: ${task.id}. Initializing connection.`);
                if (ws) ws.close();
                clearTimeout(reconnectTimeout);
                reconnectAttempts = 0;
                connectionStatus = 'disconnected';
                
                if (task.logs && Array.isArray(task.logs)) {
                    console.log(`[EXT:frontend:TaskRunner] Loaded ${task.logs.length} existing logs.`);
                    taskLogs.set(task.logs);
                } else {
                    taskLogs.set([]);
                }
                
                connect();
            }
        });
        return unsubscribe;
    });
    // #endregion onMount:Function

    // #region onDestroy:Function [TYPE Function]
    onDestroy(() => {
        clearTimeout(reconnectTimeout);
        clearTimeout(dataTimeout);
        if (ws) {
            console.log("[EXT:frontend:TaskRunner][Action] Closing WebSocket connection.");
            ws.close();
        }
    });
    // #endregion onDestroy:Function
</script>

<!-- [SECTION: TEMPLATE] -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
    <!-- Card Header -->
    <div class="px-6 py-4 bg-gradient-to-r from-gray-50 to-slate-50 border-b border-gray-100">
        <div class="flex items-center justify-between">
            <div class="flex items-center gap-3">
                <div class="flex items-center justify-center w-10 h-10 rounded-lg bg-gray-100 text-gray-600">
                    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M12 20h9"></path>
                        <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
                    </svg>
                </div>
                <div>
                    <h2 class="text-lg font-semibold text-gray-900">{$t.tasks?.task_label}</h2>
                    <p class="text-sm text-gray-500">Мониторинг выполнения задач</p>
                </div>
            </div>
            
            {#if $selectedTask}
                <div class="flex items-center gap-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 font-medium text-yellow-600 bg-yellow-50 px-2.5 py-1 rounded-full">{$t.tasks?.connecting}</span>
                    {:else if connectionStatus === 'connected'}
                        <span class="h-3 w-3 rounded-full bg-green-500"></span>
                        <span class="text-xs font-medium text-green-600 bg-green-50 px-2.5 py-1 rounded-full">{$t.tasks?.live}</span>
                    {:else if connectionStatus === 'completed'}
                        <span class="h-3 w-3 rounded-full bg-blue-500"></span>
                        <span class="text-xs font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full">{$t.tasks?.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 font-medium text-orange-600 bg-orange-50 px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_mapping}</span>
                    {:else if connectionStatus === 'awaiting_input'}
                        <span class="h-3 w-3 rounded-full bg-orange-500 animate-pulse"></span>
                        <span class="text-xs font-medium text-orange-600 bg-orange-50 px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_input}</span>
                    {:else}
                        <span class="h-3 w-3 rounded-full bg-red-500"></span>
                        <span class="text-xs font-medium text-red-600 bg-red-50 px-2.5 py-1 rounded-full">{$t.tasks?.disconnected}</span>
                    {/if}
                </div>
            {/if}
        </div>
    </div>

    <!-- Card Body -->
    <div class="p-6">
        {#if $selectedTask}
            <!-- Task Info Section -->
            <div class="mb-4 bg-gray-50 rounded-lg text-sm border border-gray-200 overflow-hidden">
                <details open>
                    <summary class="cursor-pointer font-medium text-gray-700 focus:outline-none hover:text-indigo-600 px-4 py-3 hover:bg-gray-100 transition-colors">{$t.tasks?.details_parameters}</summary>
                    <div class="px-4 pb-4 pt-2 border-t border-gray-200">
                        <div class="grid grid-cols-2 gap-3 mb-3">
                            <div class="flex items-center gap-2">
                                <span class="font-semibold text-gray-600">{$t.common?.id}:</span> 
                                <span class="text-gray-900 font-mono text-xs bg-white px-2 py-0.5 rounded border border-gray-200">{$selectedTask.id}</span>
                            </div>
                            <div class="flex items-center gap-2">
                                <span class="font-semibold text-gray-600">{$t.dashboard?.status}:</span> 
                                <span class="text-gray-900">{$selectedTask.status}</span>
                            </div>
                            <div class="flex items-center gap-2">
                                <span class="font-semibold text-gray-600">{$t.tasks?.started_label}:</span> 
                                <span class="text-gray-900">{new Date($selectedTask.started_at || $selectedTask.created_at || Date.now()).toLocaleString()}</span>
                            </div>
                            <div class="flex items-center gap-2">
                                <span class="font-semibold text-gray-600">{$t.tasks?.plugin}:</span> 
                                <span class="text-gray-900">{$selectedTask.plugin_id}</span>
                            </div>
                        </div>
                        <div class="mt-3">
                            <span class="font-semibold text-gray-600">{$t.tasks?.parameters}:</span>
                            <pre class="text-xs bg-white p-3 rounded-lg mt-2 overflow-x-auto border border-gray-200 shadow-inner font-mono">{JSON.stringify($selectedTask.params, null, 2)}</pre>
                        </div>
                    </div>
                </details>
            </div>

            <!-- Log Panel -->
            <div class="h-[500px]">
                <TaskLogPanel 
                    taskId={$selectedTask.id}
                    logs={$taskLogs}
                    autoScroll={true}
                    onfilterchange={handleFilterChange}
                />
                
                {#if waitingForData && connectionStatus === 'connected'}
                    <div class="flex items-center gap-2 text-gray-500 italic mt-3 animate-pulse text-xs">
                        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <circle cx="12" cy="12" r="10"></circle>
                            <polyline points="12 6 12 12 16 14"></polyline>
                        </svg>
                        {$t.tasks?.waiting_logs}
                    </div>
                {/if}
            </div>
        {:else}
            <!-- Empty State -->
            <div class="flex flex-col items-center justify-center py-12 px-6 text-center">
                <div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
                    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                </div>
                <h3 class="text-lg font-medium text-gray-900 mb-2">{$t.tasks?.select_task}</h3>
                <p class="text-sm text-gray-500 max-w-xs">Запустите задачу маппинга, чтобы увидеть логи и детали выполнения</p>
            </div>
        {/if}
    </div>
</div>

<MissingMappingModal
    bind:show={showMappingModal}
    sourceDbName={missingDbInfo.name}
    sourceDbUuid={missingDbInfo.uuid}
    {targetDatabases}
    onresolve={handleMappingResolve}
    oncancel={() => { connectionStatus = 'disconnected'; ws.close(); }}
/>

<PasswordPrompt
    bind:show={showPasswordPrompt}
    databases={passwordPromptData.databases}
    errorMessage={passwordPromptData.errorMessage}
    onresume={handlePasswordResume}
    oncancel={() => { showPasswordPrompt = false; }}
/>
<!-- [/SECTION] -->

<!-- #endregion TaskRunner -->
