// OS input injection layer.
//
// Cross-platform mouse/keyboard control via @nut-tree-fork/nut-js (optional
// native dependency). If nut-js isn't installed (e.g. CI, or a sandbox without
// a display), this module degrades to a logging no-op so the rest of the agent
// still runs and can be tested. On Windows, nut-js drives the Win32 SendInput
// API under the hood — the same mechanism TeamViewer/AnyDesk use.

let nut = null;
try {
  // eslint-disable-next-line import/no-extraneous-dependencies
  nut = require('@nut-tree-fork/nut-js');
  nut.mouse.config.autoDelayMs = 0;
  nut.keyboard.config.autoDelayMs = 0;
} catch {
  nut = null;
}

const available = !!nut;

// Map browser KeyboardEvent.key values to nut-js Key enum names.
function mapKey(key, code) {
  if (!nut) return null;
  const K = nut.Key;
  const direct = {
    'Enter': K.Enter, 'Backspace': K.Backspace, 'Tab': K.Tab, 'Escape': K.Escape,
    ' ': K.Space, 'ArrowLeft': K.Left, 'ArrowRight': K.Right, 'ArrowUp': K.Up, 'ArrowDown': K.Down,
    'Home': K.Home, 'End': K.End, 'PageUp': K.PageUp, 'PageDown': K.PageDown, 'Delete': K.Delete,
    'Control': K.LeftControl, 'Shift': K.LeftShift, 'Alt': K.LeftAlt, 'Meta': K.LeftSuper,
    'CapsLock': K.CapsLock,
  };
  if (direct[key] !== undefined) return [direct[key]];
  if (/^F\d{1,2}$/.test(key) && K[key] !== undefined) return [K[key]];
  if (key && key.length === 1) {
    const upper = key.toUpperCase();
    if (/[A-Z]/.test(upper) && K[upper] !== undefined) return [K[upper]];
    if (/[0-9]/.test(key) && K['Num' + key] !== undefined) return [K['Num' + key]];
    // Fall back to typing the literal character (handles symbols/shifted chars)
    return { type: key };
  }
  return null;
}

// Cache the screen size (this nut-js exposes screen.width()/height(), not getResolution()).
// Recomputed once per session (cleared in releaseAll) so a resolution change is picked up.
let _screen = null;
async function screenSize() {
  if (!_screen) _screen = { w: await nut.screen.width(), h: await nut.screen.height() };
  return _screen;
}
async function moveTo(xNorm, yNorm) {
  if (!nut) return;
  const { w, h } = await screenSize();
  await nut.mouse.setPosition(new nut.Point(Math.round(xNorm * w), Math.round(yNorm * h)));
}

function buttonEnum(b) {
  if (!nut) return null;
  return b === 2 ? nut.Button.RIGHT : b === 1 ? nut.Button.MIDDLE : nut.Button.LEFT;
}

const pressed = new Set();

// Inject a single normalized input event coming from the viewer.
async function inject(evt) {
  if (!nut) {
    if (evt.kind !== 'mousemove') console.log('[input:noop]', JSON.stringify(evt));
    return;
  }
  try {
    switch (evt.kind) {
      case 'mousemove':
        await moveTo(evt.x, evt.y); break;
      case 'mousedown':
        await moveTo(evt.x, evt.y); await nut.mouse.pressButton(buttonEnum(evt.button)); break;
      case 'mouseup':
        await nut.mouse.releaseButton(buttonEnum(evt.button)); break;
      case 'dblclick':
        await moveTo(evt.x, evt.y); await nut.mouse.doubleClick(nut.Button.LEFT); break;
      case 'scroll':
        if (evt.dy) await (evt.dy > 0 ? nut.mouse.scrollDown(Math.abs(evt.dy)) : nut.mouse.scrollUp(Math.abs(evt.dy)));
        if (evt.dx) await (evt.dx > 0 ? nut.mouse.scrollRight(Math.abs(evt.dx)) : nut.mouse.scrollLeft(Math.abs(evt.dx)));
        break;
      case 'keydown': {
        const m = mapKey(evt.key, evt.code);
        if (!m) break;
        if (m.type) { await nut.keyboard.type(m.type); break; }
        await nut.keyboard.pressKey(...m); m.forEach((k) => pressed.add(k));
        break;
      }
      case 'keyup': {
        const m = mapKey(evt.key, evt.code);
        if (!m || m.type) break;
        await nut.keyboard.releaseKey(...m); m.forEach((k) => pressed.delete(k));
        break;
      }
    }
  } catch (e) {
    console.error('[input] inject error:', e.message);
  }
}

// Safety: release any stuck modifier keys when a session ends.
async function releaseAll() {
  if (!nut) { pressed.clear(); return; }
  for (const k of pressed) { try { await nut.keyboard.releaseKey(k); } catch {} }
  pressed.clear();
  _screen = null;
}

module.exports = { inject, releaseAll, available, mapKey };
