import { STRUCT, ZONE, BUILDINGS } from './config.js';
import { lineTiles } from './utils.js';

export const TOOL = {
  NONE: 'none',
  BULLDOZE: 'bulldoze',
  ROAD: 'road',
  QUERY: 'query',
  ZONE_RES: 'zone_res',
  ZONE_COM: 'zone_com',
  ZONE_IND: 'zone_ind'
};

/**
 * Pointer input: tile picking, tool application (paint / drag-line /
 * rectangle), ghost previews and camera-control arbitration.
 */
export class Input {
  constructor(parts) {
    this.game = parts.game;
    this.renderer = parts.renderer;
    this.city = parts.city;
    this.ui = parts.ui;
    this.audio = parts.audio;

    this.tool = TOOL.NONE;
    this.buildSid = null;

    this.dragging = false;       // active paint/commit gesture
    this.dragKind = null;        // 'line' | 'rect' | 'paint' | 'place'
    this.anchor = null;          // start tile
    this.lastPaint = null;
    this.touches = new Set();

    const dom = this.renderer.domElement;
    dom.style.touchAction = 'none';

    dom.addEventListener('pointerdown', (e) => this._down(e));
    dom.addEventListener('pointermove', (e) => this._move(e));
    this._onUp = (e) => this._up(e);
    window.addEventListener('pointerup', this._onUp);
    window.addEventListener('pointercancel', this._onUp);
    dom.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      this.setTool(TOOL.NONE);
    });
    dom.addEventListener('wheel', (e) => e.preventDefault(), { passive: false });

    // keyboard panning (WASD / arrows)
    this.keys = new Set();
    this._onKeyDown = (e) => { this.keys.add(e.code); };
    this._onKeyUp = (e) => { this.keys.delete(e.code); };
    window.addEventListener('keydown', this._onKeyDown);
    window.addEventListener('keyup', this._onKeyUp);
  }

  destroy() {
    window.removeEventListener('pointerup', this._onUp);
    window.removeEventListener('pointercancel', this._onUp);
    window.removeEventListener('keydown', this._onKeyDown);
    window.removeEventListener('keyup', this._onKeyUp);
  }

  /** Per-frame keyboard panning along the ground plane. */
  updatePan(dt) {
    if (!this.keys.size) return;
    const k = this.keys;
    const fwdKey = (k.has('KeyW') || k.has('ArrowUp') ? 1 : 0) - (k.has('KeyS') || k.has('ArrowDown') ? 1 : 0);
    const sideKey = (k.has('KeyD') || k.has('ArrowRight') ? 1 : 0) - (k.has('KeyA') || k.has('ArrowLeft') ? 1 : 0);
    if (!fwdKey && !sideKey) return;
    const cam = this.renderer.camera;
    const t = this.renderer.controls.target;
    const dx = cam.position.x - t.x, dz = cam.position.z - t.z;
    const len = Math.hypot(dx, dz) || 1;
    const fx = -dx / len, fz = -dz / len;          // camera-forward on ground
    const rx = -fz, rz = fx;                        // camera-right
    const sp = Math.max(10, len * 0.9) * dt * (k.has('ShiftLeft') || k.has('ShiftRight') ? 2.2 : 1);
    const mx = (fx * fwdKey + rx * sideKey) * sp;
    const mz = (fz * fwdKey + rz * sideKey) * sp;
    cam.position.x += mx; cam.position.z += mz;
    t.x += mx; t.z += mz;
  }

  setTool(tool, sid = null) {
    this.tool = tool;
    this.buildSid = sid;
    this.renderer.clearGhost();
    this.renderer.hideDragRect();
    this.renderer.setHover(null, false);
    this.game.ui?.onToolChanged(tool, sid);
  }

  _primaryButton(e) {
    if (e.pointerType === 'touch') return true; // single finger paints
    return e.button === 0;
  }

  _down(e) {
    this.touches.add(e.pointerId);
    if (!this._primaryButton(e)) return;
    // second finger while painting → hand over to camera controls
    if (e.pointerType === 'touch' && this.touches.size > 1) {
      this._cancelGesture();
      return;
    }
    const tile = this.renderer.pickTile(e.clientX, e.clientY);
    if (!tile) return;

    this.controlsEnabled(false);
    this.dragging = true;
    this.anchor = tile;
    this.lastPaint = tile;

    switch (this.tool) {
      case TOOL.BULLDOZE:
        this.dragKind = 'paint';
        this._applyBulldoze(tile);
        break;
      case TOOL.ROAD:
        this.dragKind = 'line';
        break;
      case TOOL.ZONE_RES:
      case TOOL.ZONE_COM:
      case TOOL.ZONE_IND:
        this.dragKind = 'rect';
        break;
      case TOOL.QUERY:
        this.dragKind = null;
        this.dragging = false;
        this.controlsEnabled(true);
        this.game.queryTile(tile, e.clientX, e.clientY);
        break;
      default:
        if (this.buildSid) {
          this.dragKind = 'place';
          this._tryPlace(this.buildSid, tile);
        } else {
          this.dragKind = null;
          this.dragging = false;
          this.controlsEnabled(true);
        }
    }
  }

  _move(e) {
    const tile = this.renderer.pickTile(e.clientX, e.clientY);
    this.renderer.setHover(tile, !!tile && !this.dragging);

    // ghost preview for buildings
    if (!this.dragging && this.buildSid && tile) {
      const check = this.city.canPlaceStruct(this.buildSid, tile.x, tile.z);
      const valid = check.ok && this.city.money >= check.cost;
      this.renderer.setGhost(this.buildSid, tile, valid);
      this.ui.showTooltip(e.clientX, e.clientY,
        valid ? `${BUILDINGS[this.buildSid].name} — $${check.cost.toLocaleString()}` : check.reason || 'Cannot place here');
    } else if (!this.dragging) {
      this.renderer.clearGhost();
      this.ui.hideTooltip();
    }

    if (!this.dragging || !tile) return;

    switch (this.dragKind) {
      case 'line': {
        const pts = lPathTiles(this.anchor, tile);
        let cost = 0, ok = true;
        for (const [x, z] of pts) {
          const c = this.city.canPlaceStruct(STRUCT.ROAD, x, z);
          if (c.ok) cost += c.cost;
          else if (c.reason !== 'Tile occupied — bulldoze first') ok = false;
        }
        const first = pts[0], last = pts[pts.length - 1];
        this.renderer.showDragRect(first[0], first[1], last[0], last[1], ok);
        this.ui.showTooltip(e.clientX, e.clientY, `Road ×${pts.length} — $${cost.toLocaleString()}`);
        break;
      }
      case 'rect': {
        let n = 0, bad = 0;
        const x0 = Math.min(this.anchor.x, tile.x), x1 = Math.max(this.anchor.x, tile.x);
        const z0 = Math.min(this.anchor.z, tile.z), z1 = Math.max(this.anchor.z, tile.z);
        for (let z = z0; z <= z1; z++) for (let x = x0; x <= x1; x++) {
          const i = this.city.grid.idx(x, z);
          if (this.city.grid.terrain[i] === 1 || !this.city.tileFree(i)) bad++;
          else n++;
        }
        this.renderer.showDragRect(this.anchor.x, this.anchor.z, tile.x, tile.z, bad === 0);
        this.ui.showTooltip(e.clientX, e.clientY, `Zone ${n} tile${n === 1 ? '' : 's'} (free)`);
        break;
      }
      case 'paint': {
        if (tile.x !== this.lastPaint.x || tile.z !== this.lastPaint.z) {
          for (const [x, z] of lineTiles(this.lastPaint.x, this.lastPaint.z, tile.x, tile.z)) {
            this._applyBulldoze({ x, z });
          }
          this.lastPaint = tile;
        }
        break;
      }
      case 'place': {
        const check = this.city.canPlaceStruct(this.buildSid, tile.x, tile.z);
        const valid = check.ok && this.city.money >= check.cost;
        this.renderer.setGhost(this.buildSid, tile, valid);
        if (valid) this._tryPlace(this.buildSid, tile); // drag to stamp several
        break;
      }
    }
  }

  _up(e) {
    this.touches.delete(e.pointerId);
    if (!this.dragging) return;
    if (e.type === 'pointercancel') { this._cancelGesture(); return; }
    const tile = this.renderer.pickTile(e.clientX, e.clientY);

    switch (this.dragKind) {
      case 'line': {
        if (tile) {
          const pts = lPathTiles(this.anchor, tile);
          let built = 0, spent = 0;
          for (const [x, z] of pts) {
            const c = this.city.canPlaceStruct(STRUCT.ROAD, x, z);
            if (!c.ok) continue;
            if (this.city.money < c.cost) { this.ui.toast('Not enough funds for the full route.', 'warn'); break; }
            const r = this.city.placeStruct(STRUCT.ROAD, x, z);
            if (r.ok) { built++; spent += c.cost; }
          }
          if (built) this.audio.place();
          else if (pts.length) this.audio.error();
        }
        break;
      }
      case 'rect': {
        if (tile) {
          const zid = this.tool === TOOL.ZONE_RES ? ZONE.RES
            : this.tool === TOOL.ZONE_COM ? ZONE.COM : ZONE.IND;
          const x0 = Math.min(this.anchor.x, tile.x), x1 = Math.max(this.anchor.x, tile.x);
          const z0 = Math.min(this.anchor.z, tile.z), z1 = Math.max(this.anchor.z, tile.z);
          const tiles = [];
          for (let z = z0; z <= z1; z++) for (let x = x0; x <= x1; x++) tiles.push([x, z]);
          const placed = this.city.placeZone(zid, tiles);
          if (placed > 0) this.audio.zone();
          this.game.checkQuests?.();
        }
        break;
      }
      case 'place':
        this.game.checkQuests?.();
        break;
    }

    this.dragging = false;
    this.dragKind = null;
    this.renderer.hideDragRect();
    this.controlsEnabled(true);
  }

  _cancelGesture() {
    this.dragging = false;
    this.dragKind = null;
    this.renderer.hideDragRect();
    this.renderer.clearGhost();
    this.controlsEnabled(true);
  }

  _applyBulldoze(tile) {
    const r = this.city.demolish(tile.x, tile.z);
    if (r.ok) this.audio.bulldoze();
    else if (r.reason === 'Not enough funds') {
      this.ui.toast('Not enough funds to demolish.', 'warn');
      this._cancelGesture();
    }
  }

  _tryPlace(sid, tile) {
    const check = this.city.canPlaceStruct(sid, tile.x, tile.z);
    if (!check.ok) { this.audio.error(); this.ui.flashTooltip(check.reason); return; }
    if (this.city.money < check.cost) { this.audio.error(); this.ui.flashTooltip('Not enough funds'); return; }
    const r = this.city.placeStruct(sid, tile.x, tile.z);
    if (r.ok) {
      this.audio.place();
      // friendly heads-up for service buildings without road access
      const meta = BUILDINGS[sid];
      if (meta.radius >= 10 && meta.cat !== 'power' && !this.city.roadNear(tile.x, tile.z, 2)) {
        this.ui.toast(`${meta.name} has no road access — it will stay inactive until you connect it.`, 'warn');
      }
      this.game.checkQuests?.();
    }
  }

  controlsEnabled(on) {
    this.renderer.controls.enabled = on;
  }
}

/** L-shaped path: horizontal first, then vertical. */
function lPathTiles(a, b) {
  const pts = [];
  const sx = Math.sign(b.x - a.x), sz = Math.sign(b.z - a.z);
  for (let x = a.x; x !== b.x + sx; x += sx || 1) {
    if (sx === 0) break;
    pts.push([x, a.z]);
  }
  if (sx === 0) pts.push([a.x, a.z]);
  for (let z = a.z + (sz || 1); ; z += sz || 1) {
    if (sz === 0) break;
    pts.push([b.x, z]);
    if (z === b.z) break;
  }
  return pts.length ? pts : [[a.x, a.z]];
}
