import type { GameWebSocket, ServerMessage } from '../network/websocket';
import type { GameEngine } from './engine';
import type {
  HeroMovePayload,
  PositionSyncPayload,
  RouteAssignedPayload,
  CombatStartPayload,
  AttackPayload,
  CombatEndPayload,
  HeroDiedPayload,
  HeroRevivedPayload,
  BuffAppliedPayload,
  TownEnterPayload,
  TownNPCVisitPayload,
  AdventureLogLinePayload,
  NPCEncounterPayload,
  NPCEncounterEndPayload,
  LevelUpPayload,
  EquipmentChangePayload,
  PotionCollectedPayload,
  QuestProgressPayload,
  QuestCompletePayload,
  QuestAvailablePayload,
  ServerErrorPayload,
  EnemyState,
  LootDrop,
  MerchantLootPayload,
} from './types';
import { EnemyType, Rarity } from './types';

// ---- Callback types for UI layer (App.tsx) ----

export interface WSHandlerCallbacks {
  onCombatEnd?: (payload: CombatEndPayload) => void;
  onHeroDied?: (payload: HeroDiedPayload) => void;
  onHeroRevived?: (payload: HeroRevivedPayload) => void;
  onBuffApplied?: (payload: BuffAppliedPayload) => void;
  onTownEnter?: (payload: TownEnterPayload) => void;
  onTownNPCVisit?: (payload: TownNPCVisitPayload) => void;
  onAdventureLogLine?: (payload: AdventureLogLinePayload) => void;
  onTownExit?: () => void;
  onNPCEncounter?: (payload: NPCEncounterPayload) => void;
  onNPCEncounterEnd?: (payload: NPCEncounterEndPayload) => void;
  onLevelUp?: (payload: LevelUpPayload) => void;
  onEquipmentChange?: (payload: EquipmentChangePayload) => void;
  onPotionCollected?: (payload: PotionCollectedPayload) => void;
  onQuestProgress?: (payload: QuestProgressPayload) => void;
  onQuestComplete?: (payload: QuestCompletePayload) => void;
  onQuestAvailable?: (payload: QuestAvailablePayload) => void;
  onError?: (payload: ServerErrorPayload) => void;
  onHeroStateReceived?: (hero: Record<string, unknown>) => void;
  onMerchantLoot?: (payload: MerchantLootPayload) => void;
}

/**
 * Bridges WebSocket messages to engine state updates and UI callbacks.
 *
 * This is a stateless dispatcher: it parses typed envelopes from the
 * server and calls the appropriate engine method or UI callback.
 * No game logic lives here -- just routing.
 */
export function wireWSHandler(
  ws: GameWebSocket,
  engine: GameEngine,
  callbacks: WSHandlerCallbacks,
): void {
  // ---- Server -> Client: Movement ----

  ws.on('hero_move', (msg: ServerMessage) => {
    const p = msg.payload as HeroMovePayload;
    engine.applyHeroMove(p.x, p.y, p.targetX, p.targetY, p.speed);
  });

  ws.on('position_sync', (msg: ServerMessage) => {
    const p = msg.payload as PositionSyncPayload;
    engine.applyPositionSync(p.x, p.y, p.state);
  });

  ws.on('route_assigned', (msg: ServerMessage) => {
    const p = msg.payload as RouteAssignedPayload;
    engine.applyRouteAssigned(p.waypoints, p.speed);
  });

  // ---- Server -> Client: Hero state snapshot ----

  ws.on('hero_state', (msg: ServerMessage) => {
    const payload = msg.payload as Record<string, unknown>;
    callbacks.onHeroStateReceived?.(payload);
  });

  // ---- Server -> Client: Combat ----

  ws.on('combat_start', (msg: ServerMessage) => {
    const p = msg.payload as CombatStartPayload;
    const enemy: EnemyState = {
      id: Date.now(),
      name: p.enemy.name,
      hp: p.enemy.hp,
      maxHp: p.enemy.maxHp,
      position: { x: 0, y: 0 }, // engine will position relative to hero
      attackSpeed: p.enemy.speed,
      damage: p.enemy.attack,
      defense: p.enemy.defense,
      enemyType: (p.enemy.type as EnemyType) || EnemyType.Wolf,
    };
    engine.applyCombatStart(enemy);
  });

  ws.on('attack', (msg: ServerMessage) => {
    const p = msg.payload as AttackPayload;
    engine.applyAttack(p.source, p.damage, p.isCrit, p.heroHp, p.enemyHp);
  });

  ws.on('combat_end', (msg: ServerMessage) => {
    const p = msg.payload as CombatEndPayload;
    engine.applyCombatEnd();
    callbacks.onCombatEnd?.(p);
  });

  ws.on('merchant_loot', (msg: ServerMessage) => {
    const p = msg.payload as MerchantLootPayload;
    callbacks.onMerchantLoot?.(p);
  });

  // ---- Server -> Client: Death / Revive ----

  ws.on('hero_died', (msg: ServerMessage) => {
    const p = msg.payload as HeroDiedPayload;
    engine.applyHeroDied();
    callbacks.onHeroDied?.(p);
  });

  ws.on('hero_revived', (msg: ServerMessage) => {
    const p = msg.payload as HeroRevivedPayload;
    engine.applyHeroRevived(p.hp);
    callbacks.onHeroRevived?.(p);
  });

  // ---- Server -> Client: Buffs ----

  ws.on('buff_applied', (msg: ServerMessage) => {
    const p = msg.payload as BuffAppliedPayload;
    callbacks.onBuffApplied?.(p);
  });

  // ---- Server -> Client: Town ----

  ws.on('town_enter', (msg: ServerMessage) => {
    const p = msg.payload as TownEnterPayload;
    engine.applyTownEnter(p.townId, p.buildings as any);
    callbacks.onTownEnter?.(p);
  });

  ws.on('town_exit', () => {
    engine.applyTownExit();
    callbacks.onTownExit?.();
  });

  ws.on('town_npc_visit', (msg: ServerMessage) => {
    const p = msg.payload as TownNPCVisitPayload;
    engine.applyTownNPCVisit(p.name, p.type);
    callbacks.onTownNPCVisit?.(p);
  });

  ws.on('adventure_log_line', (msg: ServerMessage) => {
    const p = msg.payload as AdventureLogLinePayload;
    engine.applyAdventureLogLine(p.message);
    callbacks.onAdventureLogLine?.(p);
  });

  // ---- Server -> Client: NPC Encounter ----

  ws.on('npc_encounter', (msg: ServerMessage) => {
    const p = msg.payload as NPCEncounterPayload;
    callbacks.onNPCEncounter?.(p);
  });

  ws.on('npc_encounter_end', (msg: ServerMessage) => {
    const p = msg.payload as NPCEncounterEndPayload;
    callbacks.onNPCEncounterEnd?.(p);
  });

  // ---- Server -> Client: Progression ----

  ws.on('level_up', (msg: ServerMessage) => {
    const p = msg.payload as LevelUpPayload;
    callbacks.onLevelUp?.(p);
  });

  ws.on('equipment_change', (msg: ServerMessage) => {
    const p = msg.payload as EquipmentChangePayload;
    callbacks.onEquipmentChange?.(p);
  });

  ws.on('potion_collected', (msg: ServerMessage) => {
    const p = msg.payload as PotionCollectedPayload;
    callbacks.onPotionCollected?.(p);
  });

  // ---- Server -> Client: Quests ----

  ws.on('quest_progress', (msg: ServerMessage) => {
    const p = msg.payload as QuestProgressPayload;
    callbacks.onQuestProgress?.(p);
  });

  ws.on('quest_complete', (msg: ServerMessage) => {
    const p = msg.payload as QuestCompletePayload;
    callbacks.onQuestComplete?.(p);
  });

  ws.on('quest_available', (msg: ServerMessage) => {
    const p = msg.payload as QuestAvailablePayload;
    callbacks.onQuestAvailable?.(p);
  });

  // ---- Server -> Client: Error ----

  ws.on('error', (msg: ServerMessage) => {
    const p = msg.payload as ServerErrorPayload;
    console.warn('[WS] Server error:', p.code, p.message);
    callbacks.onError?.(p);
  });
}

// ---- Client -> Server command helpers ----

export function sendActivateBuff(ws: GameWebSocket, buffType: string): void {
  ws.send('activate_buff', { buffType });
}

export function sendUsePotion(ws: GameWebSocket): void {
  ws.send('use_potion', {});
}

export function sendRevive(ws: GameWebSocket): void {
  ws.send('revive', {});
}

export function sendAcceptQuest(ws: GameWebSocket, questId: number): void {
  ws.send('accept_quest', { questId });
}

export function sendClaimQuest(ws: GameWebSocket, questId: number): void {
  ws.send('claim_quest', { questId });
}

export function sendNPCInteract(ws: GameWebSocket, npcId: number): void {
  ws.send('npc_interact', { npcId });
}

export function sendNPCAlmsAccept(ws: GameWebSocket): void {
  ws.send('npc_alms_accept', {});
}

export function sendNPCAlmsDecline(ws: GameWebSocket): void {
  ws.send('npc_alms_decline', {});
}

/**
 * Build a LootDrop from combat_end payload for the loot popup UI.
 */
export function buildLootFromCombatEnd(p: CombatEndPayload): LootDrop | null {
  if (p.goldGained <= 0 && p.loot.length === 0) return null;

  const equip = p.loot.find(
    (l) => l.itemType === 'weapon' || l.itemType === 'armor',
  );

  return {
    itemType: 'gold',
    rarity: equip
      ? ((equip.rarity?.toLowerCase() ?? 'common') as Rarity)
      : Rarity.Common,
    goldAmount: Math.max(0, p.goldGained),
    itemName: equip?.name,
    bonusItem: equip
      ? {
          itemType: equip.itemType as 'weapon' | 'armor',
          rarity: (equip.rarity?.toLowerCase() ?? 'common') as Rarity,
          itemName: equip.name,
        }
      : undefined,
  };
}

/**
 * Build a LootDrop for the popup after a wandering merchant trade (gear only; equip or auto-sell).
 */
export function buildMerchantLootDrop(p: MerchantLootPayload): LootDrop | null {
  const rarity = (p.rarity?.toLowerCase() ?? 'common') as Rarity;
  if (p.goldAmount != null && p.goldAmount > 0) {
    return {
      itemType: 'gold',
      rarity,
      goldAmount: p.goldAmount,
      itemName: p.itemName ? `Sold: ${p.itemName}` : undefined,
    };
  }
  if (p.itemName) {
    return {
      itemType: 'gold',
      rarity,
      goldAmount: 0,
      itemName: p.itemName,
    };
  }
  return null;
}
