// MapScene.ts: base class for all 10 maps. Handles tilemap build, player spawn,
// collision, camera, interaction proximity prompt, doors, erosion FX per frame.
import Phaser from 'phaser';
import { Player } from '../entities/Player';
import { director } from '../systems/CorruptionDirector';
import { UIScene, DialogueLine } from './UIScene';
import { INTERACT_RANGE } from '../data/gamedata';
import { audio } from '../systems/AudioEngine';

export interface SpawnPoint { x: number; y: number; }

export abstract class MapScene extends Phaser.Scene {
  player!: Player;
  mapKey: string;
  mapName: string;
  collisionLayer!: Phaser.Tilemaps.TilemapLayer;
  interactables: { x:number; y:number; r:number; onInteract:()=>void; promptText?:string; enabled:()=>boolean }[] = [];
  doors: { x:number; y:number; target:string; spawn:string }[] = [];
  npcSprites: Phaser.GameObjects.Sprite[] = [];
  pendingSpawn = 'default';
  fxLayer!: Phaser.GameObjects.Rectangle;
  doorCooldown = 0;
  private _sadOff: (() => void) | null = null;

  constructor(key: string, mapKey: string, mapName: string) {
    super(key);
    this.mapKey = mapKey;
    this.mapName = mapName;
  }

  init(data: any) {
    this.pendingSpawn = (data && data.spawn) || 'default';
  }

  create() {
    const map = this.make.tilemap({ key: this.mapKey });
    const ts = map.addTilesetImage('tileset', 'tileset');
    const ground = map.createLayer('ground', ts!, 0, 0)!;
    const obs = map.createLayer('obstacles', ts!, 0, 0)!;
    obs.setCollisionByProperty({ collides: true });
    this.collisionLayer = obs;
    ground.setDepth(0);
    obs.setDepth(10);

    // gather objects
    const objLayer = map.getObjectLayer('objects');
    if (objLayer) {
      for (const o of objLayer.objects) {
        const ox = o.x!, oy = o.y!;
        const props: Record<string,string> = {};
        (o.properties||[]).forEach((p:any)=> props[p.name] = String(p.value));
        if (o.type === 'door' || o.name.startsWith('door')) {
          this.doors.push({ x: ox+8, y: oy+8, target: props.target||'', spawn: props.spawn||'default' });
        }
      }
    }

    // spawn player at a door matching pendingSpawn, else center.
    // Offset inward enough that the player lands OUTSIDE the auto-door trigger radius
    // of the door they just arrived through (prevents instant re-transition / bouncing).
    let sx = map.widthInPixels/2, sy = map.heightInPixels/2;
    if (this.pendingSpawn !== 'default') {
      const found = (objLayer?.objects||[]).find(o => o.name === this.pendingSpawn);
      if (found) {
        const dx = found.x! + 8, dy = found.y! + 8;
        // nudge toward map center (inward) by 40px — well beyond the 22px auto-door radius
        const cx = map.widthInPixels/2, cy = map.heightInPixels/2;
        const a = Math.atan2(cy - dy, cx - dx);
        sx = dx + Math.cos(a) * 40;
        sy = dy + Math.sin(a) * 40;
      }
    }
    this.player = new Player(this, sx, sy);
    // spawn immunity so auto-doors don't fire on arrival
    this.doorCooldown = this.time.now + 600;

    this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    this.physics.add.collider(this.player, obs);

    // camera
    const cam = this.cameras.main;
    cam.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    cam.startFollow(this.player, true, 0.15, 0.15);
    cam.setRoundPixels(true);

    // FX overlay rectangle for vignette/saturation tint
    this.fxLayer = this.add.rectangle(cam.width/2, cam.height/2, cam.width, cam.height, 0x000000, 0).setScrollFactor(0).setDepth(5000);

    // E interaction
    this.input.keyboard!.on('keydown-E', () => this.tryInteract());

    // §6.3: on sadness, nearest NPC turns to look at player (once)
    this._sadOff = director.on('sadness', (p: any) => {
      const key = p && p.nearestNpc;
      if (!key) return;
      const sp = this.npcSprites.find(s => (s as any).npcKey === key);
      if (sp) sp.setFlipX(this.player.x < sp.x);
    });

    // subclass setup
    this.onBuild(map, ts!);

    // reset spawn data
    this.events.once('shutdown', () => this.onShutdown());
  }

  abstract onBuild(map: Phaser.Tilemaps.Tilemap, ts: Phaser.Tilemaps.Tileset): void;

  onShutdown() {
    if (this._sadOff) { this._sadOff(); this._sadOff = null; }
    this.interactables = [];
    this.doors = [];
    this.npcSprites = [];
  }

  addInteract(tileX: number, tileY: number, onInteract: ()=>void, opts?: { promptText?: string; enabled?: ()=>boolean; w?: number; h?: number }) {
    const px = tileX*16 + (opts&&opts.w? (opts.w*16)/2 : 8);
    const py = tileY*16 + (opts&&opts.h? (opts.h*16)/2 : 8);
    this.interactables.push({ x: px, y: py, r: INTERACT_RANGE+8, onInteract, promptText: opts&&opts.promptText, enabled: opts&&opts.enabled ? opts.enabled : () => true });
  }

  private tryInteract() {
    const ui = this.scene.get('UIScene') as UIScene;
    if (ui && ui.isDialogueActive()) return; // dialogue handles E
    // door first (generous range so the player doesn't need pixel-perfect positioning)
    for (const d of this.doors) {
      if (Phaser.Math.Distance.Between(this.player.x, this.player.y, d.x, d.y) < 28) {
        this.transitionTo(d.target, d.spawn);
        return;
      }
    }
    // interactables
    let best: typeof this.interactables[0] | undefined;
    let bestD = Infinity;
    for (const it of this.interactables) {
      if (!it.enabled()) continue;
      const dist = Phaser.Math.Distance.Between(this.player.x, this.player.y, it.x, it.y);
      if (dist < it.r && dist < bestD) { bestD = dist; best = it; }
    }
    if (best) best.onInteract();
  }

  transitionTo(target: string, spawn: string) {
    audio.sfxBlip();
    const targetKey = this.sceneKeyFor(target);
    // stop current map scene then start the target; UIScene + DebugScene stay resident
    this.scene.stop();
    this.scene.start(targetKey, { spawn });
  }

  sceneKeyFor(mapName: string): string {
    const map: Record<string,string> = {
      house:'HouseScene', plaza:'PlazaScene', bakery:'BakeryScene', shop:'ShopScene',
      clinic:'ClinicScene', lake:'LakeScene', field:'FieldScene', well_area:'WellAreaScene',
      fog_boundary:'FogBoundaryScene', white_layer:'WhiteLayerScene',
    };
    return map[mapName] || 'PlazaScene';
  }

  update(_time: number, deltaMs: number) {
    const dt = deltaMs / 1000;
    this.player.update();

    // proximity prompt
    const ui = this.scene.get('UIScene') as UIScene;
    let promptText: string | null = null;
    let onDoor = false;
    for (const d of this.doors) {
      // auto-transition when the player steps near a door (generous, so 3-tile edge gaps always trigger)
      if (this.time.now > this.doorCooldown && Phaser.Math.Distance.Between(this.player.x, this.player.y, d.x, d.y) < 22) {
        if (!ui.isDialogueActive()) { this.transitionTo(d.target, d.spawn); return; }
      }
      if (Phaser.Math.Distance.Between(this.player.x, this.player.y, d.x, d.y) < 36) { promptText = '按 E 进门'; onDoor = true; break; }
    }
    if (!promptText) {
      let bestD = Infinity; let best: typeof this.interactables[0] | undefined;
      for (const it of this.interactables) {
        if (!it.enabled()) continue;
        const dist = Phaser.Math.Distance.Between(this.player.x, this.player.y, it.x, it.y);
        if (dist < it.r && dist < bestD) { bestD = dist; best = it; }
      }
      if (best && best.promptText) promptText = best.promptText;
    }
    if (promptText) ui.showPrompt(promptText); else ui.hidePrompt();

    // erosion FX: saturation + vignette via camera postFX (Phaser 3.60+ postFX API).
    const sat = ui.getSaturation();
    const vig = ui.getVignette();
    this.applyErosionFx(sat, vig);

    // idle/发呆 detection (§6.3): 10s not moving & not at task point
    if (!this.player.moving) {
      if (this.player.idleSince === 0) this.player.idleSince = this.time.now;
      else if (!this.player.atTaskPoint && this.time.now - this.player.idleSince > 10000) {
        // trigger once per 10s window
        this.player.idleSince = this.time.now;
        director.sadness('连续 10 秒不移动且不在任务点（发呆）', this.nearestNpcKey());
      }
    } else {
      this.player.idleSince = 0;
    }
  }

  private lastSat = -1;
  private lastVig = -1;
  applyErosionFx(sat: number, vig: number) {
    if (Math.abs(sat - this.lastSat) < 0.001 && Math.abs(vig - this.lastVig) < 0.001) return;
    this.lastSat = sat; this.lastVig = vig;
    const cam = this.cameras.main as any;
    const fx = cam.postFX;
    if (!fx) return;
    try { fx.clear(); } catch (e) {}
    // Build a saturation color matrix and assign to the ColorMatrix controller's _data.
    const cm = fx.addColorMatrix();
    // standard saturation matrix (s=1 normal, <1 desaturate, >1 saturate)
    const s = sat;
    const sr = 0.213, sg = 0.715, sb = 0.072;
    const m = [
      sr*(1-s)+s, sg*(1-s),     sb*(1-s),     0, 0,
      sr*(1-s),   sg*(1-s)+s,   sb*(1-s),     0, 0,
      sr*(1-s),   sg*(1-s),     sb*(1-s)+s,   0, 0,
      0, 0, 0, 1, 0,
    ];
    if (cm._data && cm._data.length === m.length) {
      for (let i=0;i<m.length;i++) cm._data[i] = m[i];
    } else if (cm._data) {
      cm._data = m.slice();
    }
    cm._dirty = true;
    if (vig > 0) {
      fx.addVignette(0.5, 0.5, 0.5, vig);
    }
  }

  nearestNpcKey(): string | undefined {
    let best = Infinity; let key: string | undefined;
    for (const s of this.npcSprites) {
      const k = (s as any).npcKey; if (!k) continue;
      const d = Phaser.Math.Distance.Between(this.player.x, this.player.y, s.x, s.y);
      if (d < best) { best = d; key = k; }
    }
    return key;
  }

  // helper to start dialogue via UIScene
  say(lines: DialogueLine[], onDone?: ()=>void, onChoice?:(c:any)=>void) {
    const ui = this.scene.get('UIScene') as UIScene;
    ui.startDialogue(lines, onDone, onChoice);
  }
}
