import type { InputSource, InputState } from './InputState';

/**
 * Раскладка: WASD — движение, стрелки — прицельная стрельба, пробел —
 * атака по ходу движения, Tab/Q — смена оружия, R — рестарт.
 *
 * ВАЖНО: используем `event.code` (ФИЗИЧЕСКАЯ клавиша), а не `event.key`.
 * `code` не зависит от раскладки, поэтому WASD/Q/R работают и в русской
 * раскладке (где те же клавиши дают «цфыв»/«й»/«к»). Стрелки/Tab/пробел в
 * `code` называются ArrowUp/Tab/Space.
 *
 * Контроллер держит набор зажатых клавиш и «защёлкивает» однократные действия
 * (смена оружия/рестарт). Раз в кадр вызывается poll(), который собирает
 * InputState и сбрасывает однократные флаги.
 */
export class KeyboardController implements InputSource {
  private held = new Set<string>();
  private toggleWeaponEdge = false;
  private restartEdge = false;
  private inventoryEdge = false;
  private attached = false;

  private onKeyDown = (e: KeyboardEvent): void => {
    const c = e.code;
    // Однократные действия ловим по факту нажатия (не по удержанию).
    if (!this.held.has(c)) {
      if (c === 'Tab' || c === 'KeyQ') this.toggleWeaponEdge = true;
      if (c === 'KeyR') this.restartEdge = true;
      if (c === 'KeyE') this.inventoryEdge = true;
    }
    this.held.add(c);
    if (PREVENT.has(c)) e.preventDefault();
  };

  private onKeyUp = (e: KeyboardEvent): void => {
    this.held.delete(e.code);
  };

  private onBlur = (): void => {
    // Сбрасываем всё, чтобы клавиши не «залипали» при потере фокуса.
    this.held.clear();
  };

  /**
   * Сбросить весь ввод: зажатые клавиши и однократные действия. Звать на
   * границе забега (старт новой игры) — иначе клавиша, зажатая в прошлом
   * забеге, или залатченная смена оружия «перетекут» в новый.
   */
  reset(): void {
    this.held.clear();
    this.toggleWeaponEdge = false;
    this.restartEdge = false;
    this.inventoryEdge = false;
  }

  /** Подписаться на события окна. Вызывается один раз при старте. */
  attach(target: Window = window): void {
    if (this.attached) return;
    target.addEventListener('keydown', this.onKeyDown);
    target.addEventListener('keyup', this.onKeyUp);
    target.addEventListener('blur', this.onBlur);
    this.attached = true;
  }

  /** Собрать снимок ввода и сбросить однократные флаги. */
  poll(): InputState {
    const down = (code: string) => this.held.has(code);

    let moveX = 0;
    let moveY = 0;
    if (down('KeyW')) moveY -= 1;
    if (down('KeyS')) moveY += 1;
    if (down('KeyA')) moveX -= 1;
    if (down('KeyD')) moveX += 1;

    // Прицел собираем из стрелок как ВЕКТОР — поддерживает 8 направлений
    // (одновременные ArrowUp + ArrowRight дают диагональ {0,-1}+{1,0}).
    let aimX = 0;
    let aimY = 0;
    if (down('ArrowUp')) aimY -= 1;
    if (down('ArrowDown')) aimY += 1;
    if (down('ArrowLeft')) aimX -= 1;
    if (down('ArrowRight')) aimX += 1;
    const aimVec = (aimX !== 0 || aimY !== 0) ? { x: aimX, y: aimY } : null;

    const snapshot: InputState = {
      moveX,
      moveY,
      aimVec,
      attackHeld: down('Space'),
      toggleWeapon: this.toggleWeaponEdge,
      restart: this.restartEdge,
      openInventory: this.inventoryEdge,
    };

    this.toggleWeaponEdge = false;
    this.restartEdge = false;
    this.inventoryEdge = false;
    return snapshot;
  }
}

/** Физические клавиши (e.code), у которых гасим поведение браузера (скролл/таб). */
const PREVENT = new Set([
  'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space', 'Tab', 'KeyE',
]);
