class VirtualKeyboard {
  constructor(onSendText, onSendKey, onSubmitText) {
    this.onSendText = onSendText;
    this.onSendKey = onSendKey;
    // onSubmitText(text): send text AND submit (server presses Enter after the
    // paste completes). Falls back to the old text+timed-Enter if not provided.
    this.onSubmitText = onSubmitText;
    this.ctrlActive = false;
    this.ctrlLocked = false;
    this.altActive = false;
    this.altLocked = false;
    this.shiftActive = false;
    this.shiftLocked = false;
    this._repeatTimer = null;
    this._repeatInterval = null;

    this.textInput = document.getElementById('text-input');
    this.sendBtn = document.getElementById('send-btn');
    this.ctrlKey = document.getElementById('ctrl-key');
    this.altKey = document.getElementById('alt-key');
    this.shiftKey = document.getElementById('shift-key');

    this.init();
  }

  static get REPEAT_KEYS() {
    return new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageUp', 'PageDown']);
  }

  static get REPEAT_INITIAL_DELAY() { return 400; }
  static get REPEAT_INTERVAL() { return 80; }

  init() {
    // Text input — Enter inserts newline (textarea default); only send button submits.
    // Height is fixed via CSS so the keyboard doesn't shift; multi-line scrolls internally.

    // mousedown.preventDefault keeps focus on the textarea so the soft keyboard
    // stays up; the click event still fires. Without this, on mobile the first
    // tap blurs the textarea / dismisses the keyboard and the viewport reflow
    // can swallow the click — the user has to tap a second time to submit.
    this.sendBtn.addEventListener('mousedown', (e) => e.preventDefault());
    this.sendBtn.addEventListener('click', () => this.submitText());

    // Desktop: Enter = send, Shift+Enter / Cmd+Enter = newline.
    // Mobile keeps the default (Enter = newline, send button submits).
    this.textInput.addEventListener('keydown', (e) => {
      if (e.key !== 'Enter') return;
      if (window.innerWidth < 1024) return;
      if (e.shiftKey || e.metaKey) return;
      e.preventDefault();
      this.submitText();
    });

    // Ctrl modifier
    this.ctrlKey.addEventListener('click', () => this.toggleModifier('ctrl'));
    this.ctrlKey.addEventListener('dblclick', () => this.lockModifier('ctrl'));

    // Alt modifier
    this.altKey.addEventListener('click', () => this.toggleModifier('alt'));
    this.altKey.addEventListener('dblclick', () => this.lockModifier('alt'));

    // Shift modifier
    this.shiftKey.addEventListener('click', () => this.toggleModifier('shift'));
    this.shiftKey.addEventListener('dblclick', () => this.lockModifier('shift'));

    // Text shortcut keys (e.g. /compact) — type the text, then send Enter
    // separately so slash-command menus have a tick to populate before submit.
    document.querySelectorAll('.key[data-text]').forEach((btn) => {
      btn.addEventListener('click', () => {
        this._haptic();
        this.onSendText(btn.dataset.text);
        // Slash-command menus need a tick to populate before submit; give the
        // paste enough time to render (same reason as submitText's larger delay).
        setTimeout(() => this.onSendKey('Enter'), 200);
      });
    });

    // Special keys
    document.querySelectorAll('.key[data-key]').forEach((btn) => {
      if (btn.classList.contains('modifier')) return;
      const key = btn.dataset.key;
      if (VirtualKeyboard.REPEAT_KEYS.has(key)) {
        const startRepeat = () => {
          this._clearRepeat();
          this.handleKeyPress(key);
          this._repeatTimer = setTimeout(() => {
            this._repeatInterval = setInterval(() => {
              this.handleKeyPress(key);
            }, VirtualKeyboard.REPEAT_INTERVAL);
          }, VirtualKeyboard.REPEAT_INITIAL_DELAY);
        };
        btn.addEventListener('mousedown', startRepeat);
        btn.addEventListener('touchstart', startRepeat, { passive: true });
        btn.addEventListener('mouseup', () => this._clearRepeat());
        btn.addEventListener('mouseleave', () => this._clearRepeat());
        btn.addEventListener('touchend', () => this._clearRepeat());
        btn.addEventListener('touchcancel', () => this._clearRepeat());
      } else {
        btn.addEventListener('click', () => this.handleKeyPress(key));
      }
    });
  }

  _haptic() {
    if (navigator.vibrate) navigator.vibrate(8);
  }

  _clearRepeat() {
    clearTimeout(this._repeatTimer);
    clearInterval(this._repeatInterval);
    this._repeatTimer = null;
    this._repeatInterval = null;
  }

  submitText() {
    this._haptic();
    const text = this.textInput.value;
    if (text) {
      // cmux delivers send_text as a bracketed paste, so a trailing "\n" is a
      // literal newline in the TUI input box (Claude Code / Codex), not a submit.
      // The submit therefore needs a discrete Enter AFTER the paste lands.
      this.textInput.value = '';
      if (this.onSubmitText) {
        // Preferred: the server presses Enter only AFTER send_text completes
        // (awaited ordering), so there is no client-side race between the paste
        // and the Enter — fixes the "had to press Enter twice" over slow/remote
        // connections.
        this.onSubmitText(text);
      } else {
        // Fallback for an older server that doesn't understand submit: two
        // messages with a size-scaled client delay (timing-fragile).
        this.onSendText(text);
        const lineCount = (text.match(/\n/g) || []).length + 1;
        const enterDelay = Math.min(900, 300 + (lineCount - 1) * 80);
        setTimeout(() => this.onSendKey('Enter'), enterDelay);
      }
    } else {
      // Empty submit = Enter key
      this.onSendKey('Enter');
    }
    this.textInput.focus();
  }

  handleKeyPress(key) {
    this._haptic();
    // Check for pre-built shortcut keys like Ctrl-c
    if (key.startsWith('Ctrl-') || key.startsWith('Alt-') || key.startsWith('Shift-')) {
      this.onSendKey(key);
      return;
    }

    // Apply active modifiers
    let finalKey = key;
    if (this.shiftActive) {
      finalKey = `Shift-${finalKey}`;
      if (!this.shiftLocked) this.deactivateModifier('shift');
    }
    if (this.ctrlActive) {
      finalKey = `Ctrl-${finalKey}`;
      if (!this.ctrlLocked) this.deactivateModifier('ctrl');
    }
    if (this.altActive) {
      finalKey = `Alt-${finalKey}`;
      if (!this.altLocked) this.deactivateModifier('alt');
    }

    this.onSendKey(finalKey);
  }

  toggleModifier(mod) {
    this._haptic();
    if (mod === 'ctrl') {
      if (this.ctrlLocked) { this.deactivateModifier('ctrl'); return; }
      this.ctrlActive = !this.ctrlActive;
      this.ctrlKey.classList.toggle('active', this.ctrlActive);
    } else if (mod === 'alt') {
      if (this.altLocked) { this.deactivateModifier('alt'); return; }
      this.altActive = !this.altActive;
      this.altKey.classList.toggle('active', this.altActive);
    } else if (mod === 'shift') {
      if (this.shiftLocked) { this.deactivateModifier('shift'); return; }
      this.shiftActive = !this.shiftActive;
      this.shiftKey.classList.toggle('active', this.shiftActive);
    }
  }

  lockModifier(mod) {
    if (mod === 'ctrl') {
      this.ctrlActive = true;
      this.ctrlLocked = true;
      this.ctrlKey.classList.add('active', 'locked');
    } else if (mod === 'alt') {
      this.altActive = true;
      this.altLocked = true;
      this.altKey.classList.add('active', 'locked');
    } else if (mod === 'shift') {
      this.shiftActive = true;
      this.shiftLocked = true;
      this.shiftKey.classList.add('active', 'locked');
    }
  }

  deactivateModifier(mod) {
    if (mod === 'ctrl') {
      this.ctrlActive = false;
      this.ctrlLocked = false;
      this.ctrlKey.classList.remove('active', 'locked');
    } else if (mod === 'alt') {
      this.altActive = false;
      this.altLocked = false;
      this.altKey.classList.remove('active', 'locked');
    } else if (mod === 'shift') {
      this.shiftActive = false;
      this.shiftLocked = false;
      this.shiftKey.classList.remove('active', 'locked');
    }
  }

  focus() {
    this.textInput.focus();
  }
}

window.VirtualKeyboard = VirtualKeyboard;
