import { GameConfig } from "./config.js";
import { LootTables, getRarityHatchMultiplierForEggType } from "./loot-tables.js";
import { plotSizeFromLevel } from "./grid-utils.js";
import { getPlotUpgradeCost, getWorldMapUpgradeResearchCost } from "./economy.js";
import { findOffer } from "./conveyor.js";
import { placeEgg, fillAnimalBlock } from "./placement.js";
import { getMatureBabyPlacementData, getReceptionAnimalPlacementData } from "./zoo-placement.js";
import { getNurseryCellKeysOrdered, getFreeNurseryCellKey } from "./zoo-nursery.js";

export { getNurseryCellKeysOrdered, getFreeNurseryCellKey };
/**
 * First reception cell key that has no reception animal. Returns null if none.
 * @param {import("./types.js").GameState} state
 * @returns {string | null}
 */
export function getFreeReceptionCellKey(state) {
  const usedKeys = new Set((state.receptionAnimals ?? []).map((r) => r.receptionCellKey));
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell && cell.kind === "reception" && !usedKeys.has(key)) return key;
  }
  return null;
}

/**
 * Growth duration in seconds for a baby in a nursery of the given level.
 * @param {number} nurseryLevel
 * @returns {number}
 */
export function getBabyGrowthSeconds(nurseryLevel) {
  const base = GameConfig.Nursery?.GrowthSecondsBase ?? 40;
  const level = Math.max(1, nurseryLevel ?? 1);
  return Math.max(5, Math.floor(base / level));
}

/**
 * Acclimatation duration in seconds for reception of the given level.
 * @param {number} receptionLevel
 * @returns {number}
 */
export function getAcclimatationSeconds(receptionLevel) {
  const base = GameConfig.Reception?.AcclimatationSecondsBase ?? 45;
  const level = Math.max(1, receptionLevel ?? 1);
  return Math.max(10, Math.floor(base / level));
}

/**
 * Add a baby to the first free nursery slot. Mutates state.pendingBabies and state.nextTokenId.
 * @param {import("./types.js").GameState} state
 * @param {string} animalId
 * @param {boolean} [fromOtherZoo] true if baby was bought from another zoo (conveyor/world), false if bred
 * @returns {[boolean, string?]} [ok, nurseryCellKey or reason]
 */
export function addPendingBaby(state, animalId, fromOtherZoo) {
  const key = getFreeNurseryCellKey(state);
  if (key === null || key === undefined) return [false, "NoFreeNursery"];
  if (LootTables.Animals[animalId] === null || LootTables.Animals[animalId] === undefined) return [false, "UnknownAnimal"];
  const cell = state.grid.cells[key];
  const level = cell && cell.kind === "nursery" ? (cell.level ?? 1) : 1;
  const now = Math.floor(Date.now() / 1000);
  const readyAt = now + getBabyGrowthSeconds(level);
  const id = `baby_${state.nextTokenId}`;
  state.nextTokenId += 1;
  state.pendingBabies = state.pendingBabies ?? [];
  state.pendingBabies.push({ id, animalId, nurseryCellKey: key, readyAt, fromOtherZoo: fromOtherZoo === true });
  state.lastEvolutionAt = now;
  return [true, key];
}

/**
 * Add an animal to the first free reception slot.
 * @param {import("./types.js").GameState} state
 * @param {string} animalId
 * @returns {[boolean, string?]} [ok, receptionCellKey or reason]
 */
export function addReceptionAnimal(state, animalId) {
  const key = getFreeReceptionCellKey(state);
  if (key === null || key === undefined) return [false, "NoFreeReception"];
  if (LootTables.Animals[animalId] === null || LootTables.Animals[animalId] === undefined) return [false, "UnknownAnimal"];
  const cell = state.grid.cells[key];
  const level = cell && cell.kind === "reception" ? (cell.level ?? 1) : 1;
  const now = Math.floor(Date.now() / 1000);
  const readyAt = now + getAcclimatationSeconds(level);
  const id = `reception_${state.nextTokenId}`;
  state.nextTokenId += 1;
  state.receptionAnimals = state.receptionAnimals ?? [];
  state.receptionAnimals.push({ id, animalId, receptionCellKey: key, readyAt });
  state.lastEvolutionAt = now;
  return [true, key];
}

/**
 * Buy a baby offer: pay price and add to nursery if slot free.
 * @param {import("./types.js").GameState} state
 * @param {string} animalId
 * @param {number} price
 * @returns {[boolean, string | { nurseryCellKey: string }]}
 */
export function tryBuyBaby(state, animalId, price) {
  if (state.coins < price) return [false, "NotEnoughCoins"];
  const [ok, result] = addPendingBaby(state, animalId, true);
  if (!ok) return [false, result];
  state.coins -= price;
  return [true, { nurseryCellKey: result }];
}

/**
 * Buy an animal offer: pay price and add to reception if slot free.
 * @param {import("./types.js").GameState} state
 * @param {string} animalId
 * @param {number} price
 * @returns {[boolean, string | { receptionCellKey: string }]}
 */
export function tryBuyAnimal(state, animalId, price) {
  if (state.coins < price) return [false, "NotEnoughCoins"];
  const [ok, result] = addReceptionAnimal(state, animalId);
  if (!ok) return [false, result];
  state.coins -= price;
  return [true, { receptionCellKey: result }];
}

/**
 * Place a mature baby on an empty cell. Baby must be at nurseryCellKey and readyAt <= now.
 * @param {import("./types.js").GameState} state
 * @param {{ nurseryCellKey: string, toX: number, toY: number, nowUnix: number }} opts
 * @returns {[boolean, string?]}
 */
export function placeMatureBabyOnCell(state, opts) {
  const { nurseryCellKey, toX, toY, nowUnix } = opts;
  const [ok, result] = getMatureBabyPlacementData(state, opts);
  if (!ok) return [false, result];
  fillAnimalBlock(state, toX, toY, result);
  state.pendingBabies = (state.pendingBabies ?? []).filter((p) => p.nurseryCellKey !== nurseryCellKey);
  state.lastEvolutionAt = nowUnix;
  return [true, undefined];
}

/**
 * Place a ready reception animal on an empty cell.
 * @param {import("./types.js").GameState} state
 * @param {{ receptionCellKey: string, toX: number, toY: number, nowUnix: number }} opts
 * @returns {[boolean, string?]}
 */
export function placeReceptionAnimalOnCell(state, opts) {
  const { receptionCellKey, toX, toY, nowUnix } = opts;
  const [ok, result] = getReceptionAnimalPlacementData(state, opts);
  if (!ok) return [false, result];
  fillAnimalBlock(state, toX, toY, result);
  state.receptionAnimals = (state.receptionAnimals ?? []).filter((r) => r.receptionCellKey !== receptionCellKey);
  state.lastEvolutionAt = nowUnix;
  return [true, undefined];
}

/**
 * Assign a token to the first nursery cell that has no tokenId. Mutates state.grid.cells.
 * @param {import("./types.js").GameState} state
 * @param {number} tokenId
 * @returns {boolean} true if assigned
 */
export function assignTokenToNursery(state, tokenId) {
  const keys = getNurseryCellKeysOrdered(state);
  for (const key of keys) {
    const cell = state.grid.cells[key];
    if (cell && cell.kind === "nursery" && (cell.tokenId === null || cell.tokenId === undefined)) {
      cell.tokenId = tokenId;
      return true;
    }
  }
  return false;
}

/**
 * Clear the nursery cell that holds this tokenId.
 * @param {import("./types.js").GameState} state
 * @param {number} tokenId
 */
export function clearNurseryToken(state, tokenId) {
  for (const cell of Object.values(state.grid.cells)) {
    if (cell && cell.kind === "nursery" && cell.tokenId === tokenId) {
      cell.tokenId = undefined;
      return;
    }
  }
}

/**
 * Nursery level of the cell that holds this token (for hatch duration). Returns 1 if not found.
 * @param {import("./types.js").GameState} state
 * @param {number} tokenId
 * @returns {number}
 */
export function getNurseryLevelForToken(state, tokenId) {
  for (const cell of Object.values(state.grid.cells)) {
    if (cell && cell.kind === "nursery" && cell.tokenId === tokenId) {
      return cell.level ?? 1;
    }
  }
  return 1;
}

/**
 * Hatch duration in seconds when placing from a nursery (rarity slows down, nursery level speeds up).
 * @param {string} eggType
 * @param {number} nurseryLevel
 * @returns {number}
 */
export function getHatchDurationSeconds(eggType, nurseryLevel) {
  const eggDef = LootTables.EggTypes[eggType];
  if (eggDef === null || eggDef === undefined) return 30;
  const base = eggDef.hatchSeconds;
  const rarityMult = getRarityHatchMultiplierForEggType(eggType);
  const level = Math.max(1, nurseryLevel ?? 1);
  return Math.max(5, Math.floor((base * rarityMult) / level));
}

function consumeToken(state, tokenId) {
  const idx = state.pendingEggTokens.findIndex((t) => t.tokenId === tokenId);
  if (idx < 0) return null;
  const [token] = state.pendingEggTokens.splice(idx, 1);
  return token;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {string} eggType
 * @returns {[boolean, { tokenId: number, eggType: string } | string]}
 */
export function tryBuyEgg(state, eggType) {
  const offer = findOffer(state, eggType);
  if (offer === null || offer === undefined) return [false, "OfferUnavailable"];
  const auctionBonus = Math.floor(Math.random() * (offer.price * 0.21));
  const finalPrice = offer.price + auctionBonus;
  if (state.coins < finalPrice) return [false, "NotEnoughCoins"];
  if (LootTables.EggTypes[eggType] === null || LootTables.EggTypes[eggType] === undefined) return [false, "UnknownEgg"];
  state.coins -= finalPrice;
  const token = { tokenId: state.nextTokenId, eggType, boughtAt: Math.floor(Date.now() / 1000) };
  state.nextTokenId += 1;
  state.pendingEggTokens.push(token);
  assignTokenToNursery(state, token.tokenId);
  return [true, { tokenId: token.tokenId, eggType: token.eggType }];
}

/**
 * Buy egg from laboratory offer (fixed price, no auction).
 * @param {import("./types.js").GameState} state
 * @param {string} eggType
 * @returns {[boolean, { tokenId: number, eggType: string } | string]}
 */
export function tryBuyLabEgg(state, eggType) {
  const offer = state.laboratoryOffer;
  if (offer === null || offer === undefined || offer.eggType !== eggType) return [false, "OfferUnavailable"];
  if (state.coins < offer.price) return [false, "NotEnoughCoins"];
  if (LootTables.EggTypes[eggType] === null || LootTables.EggTypes[eggType] === undefined) return [false, "UnknownEgg"];
  state.coins -= offer.price;
  state.laboratoryOffer = null;
  const token = { tokenId: state.nextTokenId, eggType, boughtAt: Math.floor(Date.now() / 1000) };
  state.nextTokenId += 1;
  state.pendingEggTokens.push(token);
  assignTokenToNursery(state, token.tokenId);
  return [true, { tokenId: token.tokenId, eggType: token.eggType }];
}

/**
 * @param {import("./types.js").GameState} state
 * @param {{ tokenId: number, x: number, y: number, nowUnix: number }} opts
 * @returns {[boolean, string?]}
 */
export function tryPlaceEgg(state, opts) {
  const { tokenId, x, y, nowUnix } = opts;
  const token = consumeToken(state, tokenId);
  if (token === null || token === undefined) return [false, "InvalidToken"];
  const eggDef = LootTables.EggTypes[token.eggType];
  if (eggDef === null || eggDef === undefined) throw new Error("ZooService: token contains unknown egg type");
  const nurseryLevel = getNurseryLevelForToken(state, tokenId);
  const hatchSeconds = getHatchDurationSeconds(token.eggType, nurseryLevel);
  const hatchAt = nowUnix + hatchSeconds;
  const seed = Math.floor(Math.random() * 2000000000) + 1;
  const [ok, reason] = placeEgg(state, { eggType: token.eggType, tokenId, x, y, hatchAt, seed });
  if (!ok) {
    state.pendingEggTokens.push(token);
    return [false, reason];
  }
  clearNurseryToken(state, tokenId);
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  if (state.stats) state.stats.eggsPlaced = (state.stats.eggsPlaced ?? 0) + 1;
  return [true, undefined];
}

/**
 * @param {import("./types.js").GameState} state
 * @returns {[boolean, string?]}
 */
export function tryUpgradePlot(state) {
  if (state.plotLevel >= GameConfig.Plot.MaxLevel) return [false, "PlotMaxLevel"];
  const cost = getPlotUpgradeCost(state.plotLevel);
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  state.plotLevel += 1;
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  const [width, height] = plotSizeFromLevel(state.plotLevel);
  state.grid.width = width;
  state.grid.height = height;
  if (state.stats) state.stats.plotUpgrades = (state.stats.plotUpgrades ?? 0) + 1;
  return [true, undefined];
}

/**
 * @param {import("./types.js").GameState} state
 * @returns {[boolean, string?]}
 */
export function tryUpgradeWorldMap(state) {
  const cfg = GameConfig.WorldMap && GameConfig.WorldMap.MapUpgrade;
  const maxLevel = cfg ? cfg.MaxLevel : 5;
  const level = state.worldMapLevel ?? 1;
  if (level >= maxLevel) return [false, "WorldMapMaxLevel"];
  const cost = getWorldMapUpgradeResearchCost(level);
  const points = state.researchPoints ?? 0;
  if (points < cost) return [false, "NotEnoughResearch"];
  state.researchPoints = points - cost;
  state.worldMapLevel = level + 1;
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}
