/**
 * ws.ts — WebSocket transport layer
 *
 * Pure connection management: connect, disconnect, reconnect, send, onMessage.
 * Delegates takeover commands to useTakeover.
 *
 * All mutable state is module-level so it survives Vite HMR.
 * useWebSocket() returns a stable API over that shared state.
 */

import { ref, type Ref } from 'vue';
import { useTakeover } from './useTakeover';
import { useHermes } from './useHermes';

interface MessagePayload {
    type: string;
    agent?: string;
    token?: string;
    user?: string;
    [key: string]: any;
}

// ── Module-level state — stored on window.__hermes to survive Vite HMR ──
const H = useHermes();
// WebSocket + ping timer
let _ws: WebSocket | null = H.ws ?? null;
let _pingInterval: ReturnType<typeof setInterval> | null = H.wsPing ?? null;
// Callbacks + buffer
const _onMessageCallbacks: ((data: any) => void)[] = H.wsCbs ?? [];
const _messageBuffer: any[] = H.wsBuf ?? [];
// Reconnect
let _reconnectTimer: ReturnType<typeof setTimeout> | null = null;
let _reconnectDelay = 1000;
let _pendingAuth = false; // WS open but agent was empty — send auth when agent is set
// Refs (re-bound on connect)
let _selectedAgentRef: Ref<string> | null = null;
let _selectedModeRef: Ref<string> | null = null;
let _isLoggedInRef: Ref<boolean> | null = null;
let _loginErrorRef: Ref<string> | null = null;
let _takeover: ReturnType<typeof useTakeover> | null = null;

const connected = H.wsConnected ?? ref(false);
const status = H.wsStatus ?? ref('Disconnected');
const currentUser = H.wsUser ?? ref('');
const sessionId = H.wsSid ?? ref<string | null>(null);
const isInitialLoad = H.wsInit ?? ref(true);

// Persist on __hermes for HMR survival
H.wsConnected = connected;
H.wsStatus = status;
H.wsUser = currentUser;
H.wsSid = sessionId;
H.wsInit = isInitialLoad;
H.wsCbs = _onMessageCallbacks;
H.wsBuf = _messageBuffer;

function send(payload: MessagePayload): void {
    if (_ws && _ws.readyState === WebSocket.OPEN) _ws.send(JSON.stringify(payload));
}

function getTakeover() {
    if (!_takeover) _takeover = useTakeover(send);
    return _takeover;
}

function getWsUrl(): string {
    if (import.meta.env.VITE_WS_URL) return import.meta.env.VITE_WS_URL as string;
    const params = new URLSearchParams(window.location.search);
    const wsHost = params.get('ws') || window.location.hostname;
    const wsPort = params.get('port') || window.location.port;
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    return `${protocol}//${wsHost}:${wsPort}/ws`;
}

function scheduleReconnect() {
    if (_reconnectTimer) return;
    if (!_isLoggedInRef?.value) return;
    status.value = `Reconnecting in ${Math.round(_reconnectDelay / 1000)}s…`;
    _reconnectTimer = setTimeout(() => {
        _reconnectTimer = null;
        if (_isLoggedInRef?.value) connect(_selectedAgentRef!, _isLoggedInRef!, _loginErrorRef!);
    }, _reconnectDelay);
    _reconnectDelay = Math.min(_reconnectDelay * 2, 16000);
}

function connect(
    selectedAgent: Ref<string>,
    isLoggedInRef: Ref<boolean>,
    loginErrorRef: Ref<string>,
    selectedMode?: Ref<string>
): void {
    if (_ws && _ws.readyState <= WebSocket.OPEN) return;
    _selectedAgentRef = selectedAgent;
    _selectedModeRef = selectedMode ?? null;
    _isLoggedInRef = isLoggedInRef;
    _loginErrorRef = loginErrorRef;
    _reconnectDelay = 1000;
    // Poll setup happens in onopen if agent is empty
    console.log('WS CONNECT attempt, ws state:', _ws?.readyState, 'url:', getWsUrl());
    const wsUrl = getWsUrl();
    if (isInitialLoad.value) {
        status.value = 'Connecting...';
        isInitialLoad.value = false;
    }

    _ws = new WebSocket(wsUrl);
    H.ws = _ws;

    _ws.onopen = () => {
        _reconnectDelay = 1000;
        const agent = selectedAgent.value;
        const token = localStorage.getItem('nyx_session') || localStorage.getItem('titan_token');
        const mode = _selectedModeRef?.value ?? 'private';
        // Send auth even without agent — BE returns ready with agent list
        _ws?.send(JSON.stringify(
            token
                ? { type: 'auth', agent: agent || '', token, mode }
                : { type: 'connect', agent: agent || '', user: 'nico', mode }
        ));
        getTakeover().reregister();
        if (_pingInterval) clearInterval(_pingInterval);
        _pingInterval = setInterval(() => {
            if (_ws?.readyState === WebSocket.OPEN) _ws.send(JSON.stringify({ type: 'ping' }));
        }, 30000);
        H.wsPing = _pingInterval;
    };

    _ws.onmessage = (event) => {
        try {
            const data = JSON.parse(event.data);
            if (data.type === 'dev_cmd' && data.cmdId && data.cmd) {
                getTakeover().dispatch(data.cmdId, data.cmd, data.args || {}, send);
                return;
            }
            if (data.type === 'error' && data.code === 'SESSION_TERMINATED') {
                console.warn('Message bounced: Session terminated.');
            } else if (data.type === 'diagnostic') {
                const logMethod = (console as any)[data.level] || console.log;
                logMethod(`Backend Diagnostic (${data.level.toUpperCase()}):`, data.message);
            }
            _messageBuffer.push(data);
            _onMessageCallbacks.forEach(fn => fn(data));
        } catch (e) {
            console.error('Parse error:', e);
        }
    };

    _ws.onclose = (e) => {
        if (_pingInterval) { clearInterval(_pingInterval); _pingInterval = null; }
        connected.value = false;
        _messageBuffer.length = 0;
        if (e.code === 4001) {
            isLoggedInRef.value = false;
            loginErrorRef.value = 'Session expired. Please log in again.';
            localStorage.removeItem('nyx_session');
            localStorage.removeItem('titan_token');
            sessionStorage.removeItem('agent');
            status.value = 'Logged out';
            // Redirect to login — avoids broken UI with stale state
            if (window.location.hash !== '#/login') {
                window.location.hash = '#/login';
            }
        } else {
            scheduleReconnect();
        }
    };

    _ws.onerror = () => {};
}

function disconnect(): void {
    if (_pingInterval) { clearInterval(_pingInterval); _pingInterval = null; H.wsPing = null; }
    if (_ws) {
        _ws.onclose = null;
        _ws.close();
        _ws = null;
        H.ws = null;
    }
    connected.value = false;
    status.value = 'Disconnected';
    currentUser.value = '';
    sessionId.value = null;
    isInitialLoad.value = true;
}

function sendDeferredAuth(): void {
    if (!_pendingAuth || !_ws || _ws.readyState !== WebSocket.OPEN) return;
    const agent = _selectedAgentRef?.value;
    if (!agent) return;
    _pendingAuth = false;
    const token = localStorage.getItem('nyx_session') || localStorage.getItem('titan_token');
    const mode = _selectedModeRef?.value ?? 'private';
    _ws.send(JSON.stringify(
        token
            ? { type: 'auth', agent, token, mode }
            : { type: 'connect', agent, user: 'nico', mode }
    ));
    getTakeover().reregister();
}

// Poll for agent becoming available after deferred auth (no Vue watch to avoid circular imports)
let _deferredAuthPoll: ReturnType<typeof setInterval> | null = null;
function setupDeferredAuthPoll() {
    if (_deferredAuthPoll) return;
    _deferredAuthPoll = setInterval(() => {
        if (!_pendingAuth) { clearInterval(_deferredAuthPoll!); _deferredAuthPoll = null; return; }
        sendDeferredAuth();
    }, 100);
}

function switchAgent(agentId: string, mode?: string): void {
    _messageBuffer.length = 0;
    send({ type: 'switch_agent', agent: agentId, mode: mode ?? _selectedModeRef?.value ?? 'private' });
}

function clearBuffer(): void {
    _messageBuffer.length = 0;
}

function onMessage(fn: (data: any) => void): () => void {
    _onMessageCallbacks.push(fn);
    return () => {
        const i = _onMessageCallbacks.indexOf(fn);
        if (i !== -1) _onMessageCallbacks.splice(i, 1);
    };
}

function replayBuffer(fn: (data: any) => void): void {
    _messageBuffer.forEach(data => fn(data));
}

export function useWebSocket() {
    return {
        connected, status, currentUser, sessionId,
        connect, disconnect, send, switchAgent, sendDeferredAuth, clearBuffer, onMessage, replayBuffer,
        getTakeover,
    };
}
