// 回响星核 / Echo Nexus — 遗迹探险肉鸽系统 (Expedition)
//
// 玩家消耗能量进入程序化生成的遗迹节点路径，沿路触发事件，
// 到达终点 boss 获取大奖。失败保留已获奖励但探险结束。
import type {
  Expedition,
  ExpeditionNode,
  ExpeditionNodeType,
  ExpeditionResult,
  GameState,
} from "./types";

/** 简单可复现随机（mulberry32） */
function makeRng(seed: number) {
  let a = seed >>> 0;
  return () => {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/** 探险配置 */
export const EXPEDITION_CONFIG = {
  /** 进入探险消耗能量 */
  energyCost: 1,
  /** 路径节点数范围 */
  minNodes: 5,
  maxNodes: 7,
  /** boss 固定在最后一个节点 */
  /** 初始生命 */
  baseHp: 100,
  /** 探险力基础 */
  basePower: 10,
  /** 战斗：胜率 = clamp(power / (power + difficulty*8), 0.25, 0.95) */
  combatDifficultyScale: 8,
  /** 能量恢复间隔（秒），每 interval 恢复 1 点 */
  energyRegenSec: 45,
};

/** 节点类型权重（boss 固定末位，其余按权重随机） */
const NODE_WEIGHTS: Record<ExpeditionNodeType, number> = {
  combat: 0.3,
  treasure: 0.25,
  choice: 0.2,
  puzzle: 0.1,
  rest: 0.15,
  boss: 0,
};

/** 节点事件文案池 */
const NODE_FLAVOR: Record<
  ExpeditionNodeType,
  { titles: string[]; descs: string[] }
> = {
  combat: {
    titles: ["守卫残影", "虚空巡游者", "腐化无人机", "以太守门人"],
    descs: [
      "一道扭曲的残影拦住去路，它是被遗忘的守卫。",
      "虚空巡游者在走廊游荡，感知到了你的存在。",
      "腐化的自治无人机识别为入侵者，启动攻击协议。",
      "以太守门人挡在门前，唯有将其击溃方可通过。",
    ],
  },
  treasure: {
    titles: ["遗失货舱", "晶体宝库", "遗物储藏室", "谐振宝箱"],
    descs: [
      "一扇半开的舱门后，散落着未及带走的晶体。",
      "宝库的能量护盾早已失效，宝藏唾手可得。",
      "储藏室里的容器还亮着微光，里面是珍贵的洞见核心。",
      "一只谐振宝箱静静等候，似乎在等故人归来。",
    ],
  },
  choice: {
    titles: ["分岔甬道", "低语祭坛", "未知装置", "回响之井"],
    descs: [
      "甬道在此分岔，两条路都传来不同的低语。",
      "祭坛上浮动着两团光，你必须选择其一。",
      "未知装置闪烁着两个按钮，你不知道后果。",
      "回响之井倒映出两种可能的未来。",
    ],
  },
  puzzle: {
    titles: ["封印之门", "谐振锁", "记忆机关", "维度栅栏"],
    descs: [
      "一道封印之门需要正确的谐振才能开启。",
      "谐振锁闪烁着复杂的图案，解之有大奖。",
      "记忆机关考验着你的解码技艺。",
      "维度栅栏以谜题为钥，智者得通行。",
    ],
  },
  rest: {
    titles: ["休憩节点", "能量泉眼", "静默之厅", "回响营地"],
    descs: [
      "一处宁静的节点，可在此恢复生命。",
      "能量泉眼涌出暖流，抚慰你的探险队。",
      "静默之厅中，时间仿佛静止，伤痛缓缓消散。",
      "前人留下的营地，尚有补给可用。",
    ],
  },
  boss: {
    titles: ["以太回响体", "维度守护者", "飞升残念", "终焉之核"],
    descs: [
      "路径尽头，以太回响体静静伫立——它是这个遗迹意志的化身。",
      "维度守护者挡在最后，唯有超越它方能触及终焉。",
      "飞升残念凝聚成形，它是以太族离去前留下的最后一道试炼。",
      "终焉之核悬浮于大厅中央，接触它即完成本次探险。",
    ],
  },
};

/** 随机选一个节点类型（boss 除外） */
function rollNodeType(rng: () => number): ExpeditionNodeType {
  const types = Object.keys(NODE_WEIGHTS) as ExpeditionNodeType[];
  const total = types.reduce((s, t) => s + NODE_WEIGHTS[t], 0);
  let r = rng() * total;
  for (const t of types) {
    r -= NODE_WEIGHTS[t];
    if (r <= 0) return t;
  }
  return "combat";
}

/** 生成一次探险 */
export function generateExpedition(
  seed: number,
  power: number,
  hp: number
): Expedition {
  const rng = makeRng(seed);
  const nodeCount =
    EXPEDITION_CONFIG.minNodes +
    Math.floor(rng() * (EXPEDITION_CONFIG.maxNodes - EXPEDITION_CONFIG.minNodes + 1));

  const nodes: ExpeditionNode[] = [];
  for (let i = 0; i < nodeCount; i++) {
    const isBoss = i === nodeCount - 1;
    const type: ExpeditionNodeType = isBoss ? "boss" : rollNodeType(rng);
    const flavor = NODE_FLAVOR[type];
    const fi = Math.floor(rng() * flavor.titles.length);
    nodes.push({
      id: i,
      type,
      title: flavor.titles[fi],
      desc: flavor.descs[fi],
      cleared: false,
      difficulty: isBoss ? 5 + Math.floor(rng() * 3) : 1 + Math.floor(rng() * 4),
      position: i,
    });
  }
  // 第一个节点固定为 treasure 或 rest（友好开局）
  if (nodes[0].type === "combat" || nodes[0].type === "boss") {
    nodes[0].type = rng() < 0.5 ? "treasure" : "rest";
    const f = NODE_FLAVOR[nodes[0].type];
    nodes[0].title = f.titles[0];
    nodes[0].desc = f.descs[0];
    nodes[0].difficulty = 1;
  }

  return {
    id: `exp_${seed}_${Date.now()}`,
    nodes,
    currentNode: 0,
    finished: false,
    rewards: { crystals: 0, insights: 0, contact: 0, fragments: [] },
    power,
    hp,
    maxHp: hp,
    seed,
    startedAt: Date.now(),
  };
}

/** 计算探险力（由技术树 + 飞升蓝图） */
export function computeExpeditionPower(state: GameState): number {
  let power = EXPEDITION_CONFIG.basePower;
  // 探险分支技术加成
  const exp1 = state.tech?.exp_1 ?? 0;
  const exp2 = state.tech?.exp_2 ?? 0;
  const exp3 = state.tech?.exp_3 ?? 0;
  power += exp1 * 3 + exp2 * 5 + exp3 * 8;
  // 飞升蓝图加成
  power *= 1 + (state.blueprints?.length ?? 0) * 0.08;
  // 飞升周目加成
  power *= 1 + (state.ascensions ?? 0) * 0.15;
  return Math.round(power);
}

/** 计算探险最大生命 */
export function computeExpeditionHp(state: GameState): number {
  let hp = EXPEDITION_CONFIG.baseHp;
  hp += (state.tech?.exp_2 ?? 0) * 20;
  hp += (state.ascensions ?? 0) * 10;
  return hp;
}

/** 战斗胜率 */
export function combatWinRate(power: number, difficulty: number): number {
  const scale = EXPEDITION_CONFIG.combatDifficultyScale;
  return Math.max(0.25, Math.min(0.95, power / (power + difficulty * scale)));
}

/** 结算当前节点（自动结算，返回结果与日志） */
export function resolveNode(
  expedition: Expedition,
  rng: () => number = Math.random
): ExpeditionResult {
  const node = expedition.nodes[expedition.currentNode];
  if (!node) return { log: "无节点", ended: true, endReason: "abort" };
  if (node.cleared) {
    return advanceExpedition(expedition);
  }

  const power = expedition.power;
  const diff = node.difficulty;

  switch (node.type) {
    case "combat": {
      const winRate = combatWinRate(power, diff);
      const won = rng() < winRate;
      if (won) {
        const crystals = 8 + diff * 6 + Math.floor(rng() * 10);
        const insights = 1 + diff + Math.floor(rng() * 2);
        node.cleared = true;
        return {
          log: `击败「${node.title}」，缴获 ${crystals} 晶体、${insights} 洞见。`,
          crystals,
          insights,
          ended: false,
        };
      }
      const hpLoss = 20 + diff * 10 + Math.floor(rng() * 15);
      const newHp = expedition.hp - hpLoss;
      if (newHp <= 0) {
        node.cleared = true;
        return {
          log: `「${node.title}」过于强大，探险队全灭…（生命 -${expedition.hp}）`,
          hpDelta: -expedition.hp,
          ended: true,
          endReason: "defeat",
        };
      }
      // 战败但存活，仍可前进（节点算通过但无奖励）
      node.cleared = true;
      return {
        log: `「${node.title}」击退了探险队，损失 ${hpLoss} 生命，狼狈通过。`,
        hpDelta: -hpLoss,
        ended: false,
      };
    }
    case "treasure": {
      const crystals = 10 + diff * 8 + Math.floor(rng() * 15);
      const insights = 2 + diff + Math.floor(rng() * 3);
      node.cleared = true;
      return {
        log: `在「${node.title}」中发现 ${crystals} 晶体、${insights} 洞见。`,
        crystals,
        insights,
        ended: false,
      };
    }
    case "rest": {
      const heal = 15 + diff * 8 + Math.floor(rng() * 10);
      node.cleared = true;
      return {
        log: `在「${node.title}」休整，恢复 ${heal} 生命。`,
        hpDelta: heal,
        ended: false,
      };
    }
    case "puzzle": {
      // 简化：探险力越高越可能解出
      const solveRate = Math.min(0.9, 0.4 + power * 0.01);
      const solved = rng() < solveRate;
      if (solved) {
        const crystals = 20 + diff * 12;
        const insights = 5 + diff * 3;
        const contact = 1 + diff * 0.5;
        node.cleared = true;
        return {
          log: `解开「${node.title}」，获得 ${crystals} 晶体、${insights} 洞见、${contact.toFixed(1)} 接触进度。`,
          crystals,
          insights,
          contact,
          ended: false,
        };
      }
      node.cleared = true;
      return {
        log: `「${node.title}」未能解开，但探险队平安通过。`,
        ended: false,
      };
    }
    case "choice": {
      // 抉择节点：随机给一个好结果或一个坏结果
      const good = rng() < 0.55;
      if (good) {
        const crystals = 6 + diff * 5 + Math.floor(rng() * 8);
        node.cleared = true;
        return {
          log: `在「${node.title}」做出了明智的选择，获得 ${crystals} 晶体。`,
          crystals,
          ended: false,
        };
      }
      const hpLoss = 8 + diff * 5;
      node.cleared = true;
      return {
        log: `在「${node.title}」的选择带来了代价，损失 ${hpLoss} 生命。`,
        hpDelta: -hpLoss,
        ended: false,
      };
    }
    case "boss": {
      // boss：必定战斗，高难度
      const winRate = combatWinRate(power, diff);
      const won = rng() < winRate;
      node.cleared = true;
      if (won) {
        const crystals = 60 + diff * 25;
        const insights = 15 + diff * 6;
        const contact = 5 + diff * 1.5;
        // boss 必给一个碎片（如果还有未解锁的）
        return {
          log: `✦ 击败「${node.title}」！获得 ${crystals} 晶体、${insights} 洞见、${contact.toFixed(1)} 接触进度。`,
          crystals,
          insights,
          contact,
          ended: true,
          endReason: "victory",
        };
      }
      return {
        log: `「${node.title}」的力量超出想象，探险队全军覆没…`,
        hpDelta: -expedition.hp,
        ended: true,
        endReason: "defeat",
      };
    }
  }
}

/** 前进到下一节点 */
export function advanceExpedition(expedition: Expedition): ExpeditionResult {
  if (expedition.currentNode >= expedition.nodes.length - 1) {
    return { log: "探险已完成", ended: true, endReason: "victory" };
  }
  expedition.currentNode++;
  return { log: `前进至节点 ${expedition.currentNode + 1}`, ended: false };
}

/** 计算能量恢复（基于时间） */
export function computeEnergyRegen(
  lastTick: number,
  now: number,
  current: number,
  max: number
): { energy: number; lastTick: number } {
  const interval = EXPEDITION_CONFIG.energyRegenSec * 1000;
  const elapsed = now - lastTick;
  const gained = Math.floor(elapsed / interval);
  if (gained <= 0) return { energy: current, lastTick };
  return {
    energy: Math.min(max, current + gained),
    lastTick: lastTick + gained * interval,
  };
}
