// terminal.js — muxterm rewrite
// Single WebSocket to muxterm, JSON control protocol, no terminal cache

// ——— Module-level state ————————————————————————————————————————————————
let _term = null;
let _fitAddon = null;
let _ws = null;
let _reconnectTimer = null;
let _overlayTimer = null;
let _currentSession = null;
let _vpHandler = null;
let _reconnectAttempts = 0;
let _searchAddon = null;
let _resizeObserver = null;
let _ctrlActive = false;
let _altActive = false;
let _muxtermPort = null;
let _muxtermToken = null;

// WASM initialization for ghostty-web
let _ghosttyReady = (typeof GhosttyWeb !== 'undefined' && GhosttyWeb.init)
  ? GhosttyWeb.init({ wasmUrl: '/vendor/ghostty-vt.wasm' })
  : Promise.resolve();

// ——— Encoding helpers ——————————————————————————————————————————————————
const _encoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null;
const _decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;

// ——— Clipboard helpers ————————————————————————————————————————————————
function _copyToClipboard(text) {
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(text).catch(function() {});
  } else {
    var ta = document.createElement('textarea');
    ta.value = text;
    ta.style.position = 'fixed';
    ta.style.left = '-9999px';
    document.body.appendChild(ta);
    ta.select();
    try { document.execCommand('copy'); } catch(e) {}
    document.body.removeChild(ta);
  }
}

// ——— WebSocket connection ————————————————————————————————————————————
/**
 * Fetch a terminal token from the API, then open a WebSocket to muxterm.
 */
function connectWebSocket() {
  var reconnectOverlay = document.getElementById('reconnect-overlay');

  fetch('/api/terminal-token', { method: 'GET' })
    .then(function(res) { return res.json(); })
    .then(function(data) {
      _muxtermPort = data.muxtermPort;
      _muxtermToken = data.token;
      _openMuxtermSocket();
    })
    .catch(function(err) {
      console.warn('muxplex: failed to fetch terminal token:', err);
      // Schedule retry
      _reconnectAttempts++;
      var delay = Math.min(1000 * Math.pow(2, _reconnectAttempts - 1), 15000);
      delay += Math.random() * 500;
      _reconnectTimer = setTimeout(connectWebSocket, delay);
    });
}

/**
 * Open a WebSocket directly to muxterm on ws://host:muxtermPort/ws?token=...
 * Sets binaryType='arraybuffer'. Handles open, message, close events.
 */
function _openMuxtermSocket() {
  var reconnectOverlay = document.getElementById('reconnect-overlay');
  var url = 'ws://' + location.hostname + ':' + _muxtermPort + '/ws?token=' + encodeURIComponent(_muxtermToken);

  var ws = new WebSocket(url);
  _ws = ws;
  ws.binaryType = 'arraybuffer';

  ws.addEventListener('open', function() {
    if (ws !== _ws) return; // stale guard
    // Reset reconnect on successful open
    _reconnectAttempts = 0;
    if (_overlayTimer) { clearTimeout(_overlayTimer); _overlayTimer = null; }
    if (reconnectOverlay) reconnectOverlay.classList.add('hidden');
    // Send attach message if we have a current session
    if (_currentSession) {
      ws.send(JSON.stringify({ attach: _currentSession }));
    }
    if (_term) _term.focus();
  });

  ws.addEventListener('message', function(e) {
    if (ws !== _ws) return; // stale guard
    if (!_term) return;
    if (e.data instanceof ArrayBuffer) {
      // Binary frame: terminal output — write directly to xterm
      var payload = new Uint8Array(e.data);
      _term.write(_decoder ? _decoder.decode(payload) : payload);
    } else if (typeof e.data === 'string') {
      // Text frame: JSON control message
      try {
        var msg = JSON.parse(e.data);
        if (msg.attached) {
          // Session attached confirmation
        } else if (msg.error) {
          console.warn('muxterm error:', msg.error);
        } else if (msg.exited) {
          // Session exited
        }
      } catch (ex) {
        // Not valid JSON — ignore
      }
    }
  });

  ws.addEventListener('close', function() {
    if (ws !== _ws) return; // stale guard
    if (!_currentSession) return; // intentional close
    // Grace period before showing overlay
    if (_overlayTimer) clearTimeout(_overlayTimer);
    _overlayTimer = setTimeout(function() {
      if (reconnectOverlay && _currentSession) reconnectOverlay.classList.remove('hidden');
      _overlayTimer = null;
    }, 1500);
    _reconnectAttempts++;
    // Exponential backoff: 1s, 2s, 4s, 8s, cap at 15s + jitter
    var delay = Math.min(1000 * Math.pow(2, _reconnectAttempts - 1), 15000);
    delay += Math.random() * 500;
    _reconnectTimer = setTimeout(connectWebSocket, delay);
  });

  ws.addEventListener('error', function() {
    if (ws !== _ws) return;
    console.warn('muxplex: WebSocket error');
  });
}

// ——— Visual viewport (mobile keyboard) ————————————————————————————————
function initVisualViewport() {
  if (!window.visualViewport) return;
  if (_vpHandler) window.visualViewport.removeEventListener('resize', _vpHandler);

  _vpHandler = function() {
    if (!_term || !_fitAddon) return;
    var container = document.getElementById('terminal-container');
    if (!container) return;
    var headerHeight = 44;
    var toolbar = document.getElementById('mobile-toolbar');
    var toolbarHeight = (toolbar && !toolbar.classList.contains('hidden')) ? toolbar.offsetHeight : 0;
    var vvh = window.visualViewport.height;
    var termHeight = Math.max(100, vvh - headerHeight - toolbarHeight);
    container.style.height = termHeight + 'px';
    window.scrollTo(0, 0);
    try { _fitAddon.fit(); } catch (_) {}
  };

  window.visualViewport.addEventListener('resize', _vpHandler);
}

// ——— Terminal creation ————————————————————————————————————————————————
/**
 * Create (or recreate) the xterm.js Terminal with addons.
 * @param {number} [fontSize=14]
 */
function createTerminal(fontSize) {
  if (_term) {
    _term.dispose();
    _term = null;
    _fitAddon = null;
  }

  var storedFontSize = (typeof fontSize === 'number' && fontSize > 0) ? fontSize : 14;
  var mobile = window.innerWidth < 600;
  var effectiveFontSize = mobile ? Math.min(storedFontSize, 12) : storedFontSize;

  var TerminalClass = (typeof GhosttyWeb !== 'undefined' && GhosttyWeb.Terminal) || window.Terminal;
  _term = new TerminalClass({
    cursorBlink: true,
    fontSize: effectiveFontSize,
    fontFamily: "'SF Mono', 'Fira Code', Consolas, monospace",
    theme: { background: '#000000', foreground: '#c9d1d9', cursor: '#58a6ff' },
    scrollback: mobile ? 500 : 5000,
    allowProposedApi: true,
    linkHandler: {
      activate: function(event, uri) { window.open(uri, '_blank'); },
    },
  });

  // FitAddon
  var FitAddonClass = (typeof GhosttyWeb !== 'undefined' && GhosttyWeb.FitAddon) || (window.FitAddon && window.FitAddon.FitAddon);
  _fitAddon = new FitAddonClass();
  _term.loadAddon(_fitAddon);

  // WebLinksAddon
  try {
    var WebLinksAddon = window.WebLinksAddon && window.WebLinksAddon.WebLinksAddon;
    if (WebLinksAddon) {
      _term.loadAddon(new WebLinksAddon(function(event, uri) {
        window.open(uri, '_blank');
      }));
    }
  } catch (e) {
    console.warn('WebLinksAddon not compatible:', e);
  }

  // SearchAddon
  try {
    var SearchAddon = window.SearchAddon && window.SearchAddon.SearchAddon;
    if (SearchAddon) {
      _searchAddon = new SearchAddon();
      _term.loadAddon(_searchAddon);
    }
  } catch (e) {
    console.warn('SearchAddon not compatible:', e);
    _searchAddon = null;
  }

  // ImageAddon
  try {
    var ImageAddon = window.ImageAddon && window.ImageAddon.ImageAddon;
    if (ImageAddon) {
      _term.loadAddon(new ImageAddon());
    }
  } catch (e) {
    console.warn('ImageAddon not compatible:', e);
  }
}

// ——— Search helpers ——————————————————————————————————————————————————
function _openSearch() {
  var bar = document.getElementById('terminal-search-bar');
  var input = document.getElementById('terminal-search-input');
  if (bar) {
    bar.classList.remove('hidden');
    if (input) { input.focus(); input.select(); }
  }
}

function _closeSearch() {
  var bar = document.getElementById('terminal-search-bar');
  if (bar) bar.classList.add('hidden');
  if (_searchAddon) _searchAddon.clearDecorations();
  if (_term) _term.focus();
}

function _searchNext() {
  var input = document.getElementById('terminal-search-input');
  if (input && input.value && _searchAddon) _searchAddon.findNext(input.value);
}

function _searchPrev() {
  var input = document.getElementById('terminal-search-input');
  if (input && input.value && _searchAddon) _searchAddon.findPrevious(input.value);
}

// ——— Open terminal ——————————————————————————————————————————————————
/**
 * Open a terminal session inside #terminal-container.
 * Creates terminal if needed, then connects or sends attach.
 * @param {string} sessionName
 * @param {string} [remoteId]  (ignored in muxterm — kept for API compat)
 * @param {number} [fontSize=14]
 */
function openTerminal(sessionName, remoteId, fontSize) {
  _currentSession = null;
  _reconnectAttempts = 0;

  if (_reconnectTimer) { clearTimeout(_reconnectTimer); _reconnectTimer = null; }
  if (_ws) { _ws.close(); _ws = null; }

  _currentSession = sessionName;

  var container = document.getElementById('terminal-container');
  if (!container) {
    console.warn('[openTerminal] #terminal-container not found');
    return;
  }

  // Ensure ghostty-web WASM is ready before creating terminal
  _ghosttyReady.then(function() {
  // Create terminal if not already present
  if (!_term) {
    createTerminal(fontSize);
    _term.open(container);

    // ResizeObserver for auto-refit
    if (_resizeObserver) { _resizeObserver.disconnect(); _resizeObserver = null; }
    if (typeof ResizeObserver !== 'undefined') {
      var _roTimer = null;
      _resizeObserver = new ResizeObserver(function() {
        clearTimeout(_roTimer);
        _roTimer = setTimeout(function() {
          if (_fitAddon) try { _fitAddon.fit(); } catch (_) {}
        }, 50);
      });
      _resizeObserver.observe(container);
    }

    // onData → send raw binary
    _term.onData(function(data) {
      if (_ws && _ws.readyState === WebSocket.OPEN) {
        var outData = data;
        // Mobile toolbar modifier keys (one-shot)
        if (_ctrlActive && data.length === 1) {
          var code = data.toUpperCase().charCodeAt(0);
          if (code >= 65 && code <= 90) outData = String.fromCharCode(code - 64);
          _ctrlActive = false;
          var cb = document.querySelector('[data-modifier="ctrl"]');
          if (cb) cb.classList.remove('mobile-toolbar__key--active');
        } else if (_altActive && data.length === 1) {
          outData = '\x1b' + data;
          _altActive = false;
          var ab = document.querySelector('[data-modifier="alt"]');
          if (ab) ab.classList.remove('mobile-toolbar__key--active');
        }
        // Send raw binary — no prefix byte
        var bytes = _encoder ? _encoder.encode(outData) : new Uint8Array(Array.from(outData).map(function(c) { return c.charCodeAt(0); }));
        _ws.send(bytes);
      }
    });

    // onResize → send JSON text frame
    _term.onResize(function(size) {
      if (_ws && _ws.readyState === WebSocket.OPEN) {
        _ws.send(JSON.stringify({ resize: { cols: size.cols, rows: size.rows } }));
      }
    });

    // Clipboard: Ctrl+Shift+C to copy, Ctrl+F to search
    _term.attachCustomKeyEventHandler(function(e) {
      if (e.type !== 'keydown') return true;
      if (e.ctrlKey && e.shiftKey && (e.key === 'C' || e.code === 'KeyC')) {
        var sel = _term.getSelection();
        if (sel) _copyToClipboard(sel);
        return false;
      }
      if (e.ctrlKey && !e.shiftKey && (e.key === 'f' || e.key === 'F' || e.code === 'KeyF')) {
        _openSearch();
        return false;
      }
      return true;
    });

    // Auto-copy selection
    _term.onSelectionChange(function() {
      var sel = _term.getSelection();
      if (sel) _copyToClipboard(sel);
    });

    // OSC 52 clipboard bridge (tmux → browser)
    _term.parser.registerOscHandler(52, function(data) {
      var parts = data.split(';');
      if (parts.length >= 2) {
        try {
          var text = atob(parts[1]);
          _copyToClipboard(text);
        } catch (e) {}
      }
      return true;
    });

    // Fit after layout
    if (_fitAddon) {
      var fitAddonRef = _fitAddon;
      var raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(fn) { fn(); };
      raf(function() {
        try { fitAddonRef.fit(); } catch (_) {}
        setTimeout(function() {
          if (_fitAddon) try { _fitAddon.fit(); } catch (_) {}
        }, 500);
      });
    }

    // Search bar wiring
    var searchInput = document.getElementById('terminal-search-input');
    var searchClose = document.getElementById('terminal-search-close');
    var searchNextBtn = document.getElementById('terminal-search-next');
    var searchPrevBtn = document.getElementById('terminal-search-prev');
    if (searchInput) {
      var newInput = searchInput.cloneNode(true);
      searchInput.parentNode.replaceChild(newInput, searchInput);
      searchInput = newInput;
      searchInput.addEventListener('input', function() {
        if (_searchAddon && searchInput.value) _searchAddon.findNext(searchInput.value);
        else if (_searchAddon) _searchAddon.clearDecorations();
      });
      searchInput.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') { e.preventDefault(); if (e.shiftKey) _searchPrev(); else _searchNext(); }
        if (e.key === 'Escape') { e.preventDefault(); _closeSearch(); }
      });
    }
    if (searchClose) { var nc = searchClose.cloneNode(true); searchClose.parentNode.replaceChild(nc, searchClose); nc.addEventListener('click', _closeSearch); }
    if (searchNextBtn) { var nn = searchNextBtn.cloneNode(true); searchNextBtn.parentNode.replaceChild(nn, searchNextBtn); nn.addEventListener('click', _searchNext); }
    if (searchPrevBtn) { var np = searchPrevBtn.cloneNode(true); searchPrevBtn.parentNode.replaceChild(np, searchPrevBtn); np.addEventListener('click', _searchPrev); }

    // Context menu suppression
    container.addEventListener('contextmenu', function(e) {
      if (e.shiftKey || e.ctrlKey || e.metaKey) return;
      e.preventDefault();
    });

    initVisualViewport();
    _initAndroidIMEFix(container);
    _initMobileToolbar();
  }

  // If WS is already open, send attach; otherwise connect
  if (_ws && _ws.readyState === WebSocket.OPEN) {
    _ws.send(JSON.stringify({ attach: sessionName }));
  } else {
    connectWebSocket();
  }
}

// ——— Close terminal ————————————————————————————————————————————————
/**
 * Close the terminal session: send detach, close WS, dispose terminal.
 */
function closeTerminal() {
  // Send detach message
  if (_ws && _ws.readyState === WebSocket.OPEN) {
    _ws.send(JSON.stringify({ detach: true }));
  }

  if (_vpHandler) {
    if (window.visualViewport) window.visualViewport.removeEventListener('resize', _vpHandler);
    _vpHandler = null;
  }

  if (_reconnectTimer) { clearTimeout(_reconnectTimer); _reconnectTimer = null; }
  if (_overlayTimer) { clearTimeout(_overlayTimer); _overlayTimer = null; }

  _currentSession = null; // set before close so close handler won't reconnect

  if (_ws) { _ws.close(); _ws = null; }
  if (_resizeObserver) { _resizeObserver.disconnect(); _resizeObserver = null; }

  _ctrlActive = false;
  _altActive = false;
  var mobileToolbar = document.getElementById('mobile-toolbar');
  if (mobileToolbar) mobileToolbar.classList.add('hidden');

  if (_term) {
    _term.dispose();
    _term = null;
    _fitAddon = null;
    _searchAddon = null;
  }

  _closeSearch();
  _reconnectAttempts = 0;
}

// ——— Font size ————————————————————————————————————————————————————
function setTerminalFontSize(size) {
  if (!_term) return;
  _term.options.fontSize = size;
  if (_fitAddon) {
    try { _fitAddon.fit(); } catch (_) {}
  }
}

// ——— Android IME fix ————————————————————————————————————————————————
function _initAndroidIMEFix(container) {
  if (!/Android/i.test(navigator.userAgent)) return;

  setTimeout(function() {
    var ta = container.querySelector('.xterm-helper-textarea');
    if (!ta) return;

    ta.addEventListener('beforeinput', function(e) {
      if (e.inputType === 'insertReplacementText') {
        e.preventDefault();
        e.stopImmediatePropagation();
        var data = e.data || '';
        if (data && _ws && _ws.readyState === WebSocket.OPEN) {
          var bytes = _encoder ? _encoder.encode('\x08' + data) : new Uint8Array(Array.from('\x08' + data).map(function(c) { return c.charCodeAt(0); }));
          _ws.send(bytes);
        }
        ta.value = '';
      }
    }, true);
  }, 100);
}

// ——— Mobile toolbar ————————————————————————————————————————————————
function _initMobileToolbar() {
  var isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
  if (!isTouchDevice) return;

  var toolbar = document.getElementById('mobile-toolbar');
  if (!toolbar) return;

  toolbar.classList.remove('hidden');
  _ctrlActive = false;
  _altActive = false;

  var ctrlBtn = toolbar.querySelector('[data-modifier="ctrl"]');
  var altBtn = toolbar.querySelector('[data-modifier="alt"]');

  toolbar.addEventListener('pointerdown', function(e) {
    var btn = e.target.closest('.mobile-toolbar__key');
    if (!btn) return;

    var modifier = btn.dataset.modifier;
    if (modifier === 'ctrl') {
      e.preventDefault();
      _ctrlActive = !_ctrlActive;
      btn.classList.toggle('mobile-toolbar__key--active', _ctrlActive);
      if (_altActive && altBtn) { _altActive = false; altBtn.classList.remove('mobile-toolbar__key--active'); }
      if (_ctrlActive && _term) _term.focus();
      return;
    }
    if (modifier === 'alt') {
      e.preventDefault();
      _altActive = !_altActive;
      btn.classList.toggle('mobile-toolbar__key--active', _altActive);
      if (_ctrlActive && ctrlBtn) { _ctrlActive = false; ctrlBtn.classList.remove('mobile-toolbar__key--active'); }
      if (_altActive && _term) _term.focus();
      return;
    }

    e.preventDefault();
    var key = btn.dataset.key;
    var input = btn.dataset.input;
    var seq = '';

    if (key) {
      switch (key) {
        case 'Escape':     seq = '\x1b'; break;
        case 'Tab':        seq = '\t'; break;
        case 'ArrowUp':    seq = '\x1b[A'; break;
        case 'ArrowDown':  seq = '\x1b[B'; break;
        case 'ArrowRight': seq = '\x1b[C'; break;
        case 'ArrowLeft':  seq = '\x1b[D'; break;
      }
    } else if (input) {
      seq = input;
    }

    if (seq && _ws && _ws.readyState === WebSocket.OPEN) {
      var bytes = _encoder ? _encoder.encode(seq) : new Uint8Array(Array.from(seq).map(function(c) { return c.charCodeAt(0); }));
      _ws.send(bytes);
    }
  });
}

// ——— Window exports ——————————————————————————————————————————————————
window._openTerminal = openTerminal;
window._switchTerminal = openTerminal;          // = openTerminal (no cache)
window._closeTerminal = closeTerminal;
window._closeAllTerminals = closeTerminal;      // = closeTerminal (no cache)
window._destroyCachedTerminal = function() {};  // no-op (no cache)
window._isTerminalCached = function() { return false; }; // always false (no cache)
window._openSearch = _openSearch;
window._closeSearch = _closeSearch;
window._setTerminalFontSize = setTerminalFontSize;
