/**
 * Food capacity and feeding tick. Animals are fed up to capacity each tick;
 * unfed animals accumulate time without food and are removed by checkDeathCauses.
 */

import { GameConfig } from "./config.js";
import { LootTables } from "./loot-tables.js";
import { isOriginCell } from "./grid-utils.js";
import { getBlockKeysFromCell } from "./placement.js";
import { getDisplayBiome, getDisplayTemperature, isAnimalAllowedOnBiome } from "./biome-rules.js";
import { getSkillLevel } from "./conveyor.js";
import { getCurrentSeason, getSeasonTemperatureModifier } from "./seasons.js";

/**
 * Total food capacity = sum over food cells of (level × AnimalsPerUnit).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
export function getFoodCapacity(state) {
  const cfg = GameConfig.Food;
  if (!cfg) return 0;
  const unit = cfg.AnimalsPerUnit ?? 5;
  let total = 0;
  for (const cell of Object.values(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "food") {
      total += (cell.level ?? 1) * unit;
    }
  }
  return total;
}

/**
 * Count origin animal cells (each animal block counts once).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
export function getOriginAnimalCount(state) {
  let n = 0;
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "animal" && isOriginCell(key, cell)) n += 1;
  }
  return n;
}

/** @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @returns {Array<{ key: string, cell: import("./types.js").AnimalCell, lastFed: number }>}
 */
function collectOriginAnimalsByLastFed(state, nowUnix) {
  const originAnimals = [];
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "animal" && isOriginCell(key, cell)) {
      originAnimals.push({ key, cell, lastFed: cell.lastFedAt ?? cell.placedAt ?? nowUnix });
    }
  }
  originAnimals.sort((a, b) => a.lastFed - b.lastFed);
  return originAnimals;
}

/**
 * Feed up to `capacity` animals this tick. Animals with oldest lastFedAt are fed first.
 * Sets lastFedAt = nowUnix on each fed animal (all cells of the block).
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 */
export function tickFeeding(state, nowUnix) {
  const capacity = getFoodCapacity(state);
  if (capacity <= 0) return;
  const originAnimals = collectOriginAnimalsByLastFed(state, nowUnix);
  let fed = 0;
  for (const { key, cell } of originAnimals) {
    if (fed >= capacity) break;
    const m = key.match(/^(\d+)_(\d+)$/);
    if (m) {
      setBlockLastFedAt(state, {
        ox: Number(m[1]),
        oy: Number(m[2]),
        w: cell.cellsWide ?? 1,
        h: cell.cellsHigh ?? 1,
        nowUnix,
      });
      fed += 1;
    }
  }
}

function setBlockLastFedAt(state, opts) {
  const { ox, oy, w, h, nowUnix } = opts;
  for (let dy = 0; dy < h; dy++) {
    for (let dx = 0; dx < w; dx++) {
      const k = `${ox + dx}_${oy + dy}`;
      const c = state.grid.cells[k];
      if (c && c.kind === "animal") c.lastFedAt = nowUnix;
    }
  }
}

/**
 * Compute feeding rate = ratio of animals that were fed this period (instantaneous:
 * fed count / total origin count). Call after tickFeeding; store in state.feedingRate for display.
 * @param {import("./types.js").GameState} state
 * @param {number} _nowUnix
 * @returns {number} 0..1
 */
export function getFeedingRate(state, _nowUnix) {
  const total = getOriginAnimalCount(state);
  if (total <= 0) return 1;
  const capacity = getFoodCapacity(state);
  const fed = Math.min(total, capacity);
  return fed / total;
}

/**
 * Remove animals and entities that meet death conditions. Increments state.deathCountRecent.
 * Causes: not visited, not fed, temperature out of range, biome not allowed,
 * research level too low for animal rarity, baby mature not placed in time, reception animal ready not placed in time.
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 */
/**
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @returns {number}
 */
function applyAnimalBlockDeaths(state, nowUnix) {
  const maxVisit = GameConfig.Visitor?.MaxSecondsWithoutVisit ?? 300;
  const maxFood = GameConfig.Food?.MaxSecondsWithoutFood ?? 120;
  const grid = state.grid;
  const cells = grid.cells;
  const blocksToRemove = collectAnimalDeathBlocks({ state, grid, cells, nowUnix, maxVisit, maxFood });
  for (const { ox, oy } of blocksToRemove) {
    const blockKeys = getBlockKeysFromCell(state, ox, oy);
    for (const k of blockKeys) delete cells[k];
  }
  return blocksToRemove.length;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @returns {number}
 */
function applyBabyAndReceptionDeaths(state, nowUnix) {
  const maxMatureNotPlaced = GameConfig.Nursery?.MaxSecondsMatureNotPlaced ?? 90;
  const maxReadyNotPlaced = GameConfig.Reception?.MaxSecondsReadyNotPlaced ?? 90;
  const babiesRemoved = filterPendingBabies(state, nowUnix, maxMatureNotPlaced);
  const receptionRemoved = filterReceptionAnimals(state, nowUnix, maxReadyNotPlaced);
  return babiesRemoved + receptionRemoved;
}

export function checkDeathCauses(state, nowUnix) {
  const n1 = applyAnimalBlockDeaths(state, nowUnix);
  const n2 = applyBabyAndReceptionDeaths(state, nowUnix);
  const total = n1 + n2;
  if (total > 0) state.deathCountRecent = (state.deathCountRecent ?? 0) + total;
}

/**
 * @param {{ state: import("./types.js").GameState, grid: { width: number, height: number }, cells: Record<string, import("./types.js").Cell>, nowUnix: number, maxVisit: number, maxFood: number }} opts
 * @returns {Array<{ ox: number, oy: number }>}
 */
function collectAnimalDeathBlocks(opts) {
  const { state, grid, cells, nowUnix, maxVisit, maxFood } = opts;
  const blocksToRemove = [];
  for (const [key, cell] of Object.entries(cells)) {
    if (cell === null || cell === undefined || cell.kind !== "animal" || !isOriginCell(key, cell)) {
      // skip
    } else {
      const def = LootTables.Animals[cell.id];
      if (def !== null && def !== undefined) {
        const entry = maybeDeathBlock({ state, key, cell, grid, nowUnix, maxVisit, maxFood, def });
        if (entry) blocksToRemove.push(entry);
      }
    }
  }
  return blocksToRemove;
}

/** @param {{ state: import("./types.js").GameState, key: string, cell: import("./types.js").AnimalCell, grid: object, nowUnix: number, maxVisit: number, maxFood: number, def: object }} opts
 * @returns {boolean}
 */
function isBlockTempAndBiomeOk(opts) {
  const { key, grid, state, def } = opts;
  const m = key.match(/^(\d+)_(\d+)$/);
  if (!m) return true;
  const ox = Number(m[1]);
  const oy = Number(m[2]);
  const cellBiome = getDisplayBiome(ox, oy, grid);
  const baseTemp = getDisplayTemperature(ox, oy, grid);
  const seasonMod = getSeasonTemperatureModifier(getCurrentSeason(state));
  const cellTemp = baseTemp + seasonMod;
  const idealTemp = def.idealTemperature ?? 18;
  const tolerance = def.temperatureTolerance ?? 5;
  const tempOk = Math.abs(cellTemp - idealTemp) <= tolerance;
  const biomeOk = isAnimalAllowedOnBiome(def.biome, cellBiome);
  return tempOk && biomeOk;
}

/** @param {{ state: import("./types.js").GameState, key: string, cell: import("./types.js").AnimalCell, grid: object, nowUnix: number, maxVisit: number, maxFood: number, def: object }} opts
 * @returns {boolean}
 */
function isBlockVisitAndFedOk(opts) {
  const { cell, nowUnix, maxVisit, maxFood } = opts;
  const lastVisited = cell.lastVisitedAt ?? cell.placedAt ?? nowUnix;
  const lastFed = cell.lastFedAt ?? cell.placedAt ?? nowUnix;
  const visitedOk = nowUnix - lastVisited < maxVisit;
  const fedOk = nowUnix - lastFed < maxFood;
  return visitedOk && fedOk;
}

/** @param {{ state: import("./types.js").GameState, key: string, cell: import("./types.js").AnimalCell, grid: object, nowUnix: number, maxVisit: number, maxFood: number, def: object }} opts
 * @returns {boolean}
 */
function isBlockEnvironmentOk(opts) {
  const { key } = opts;
  const m = key.match(/^(\d+)_(\d+)$/);
  if (!m) return true;
  return isBlockVisitAndFedOk(opts) && isBlockTempAndBiomeOk(opts);
}

function maybeDeathBlock(opts) {
  const { state, key, cell, def } = opts;
  const skillLevel = getSkillLevel(state);
  const rarityLevel = def.rarityLevel ?? 1;
  if (rarityLevel > skillLevel) return getBlockOrigin(opts);
  if (!isBlockEnvironmentOk(opts)) return getBlockOrigin(opts);
  const aloneOk = checkNotAlone(state, { originKey: key, originCell: cell, nowUnix: opts.nowUnix });
  if (!aloneOk) return getBlockOrigin(opts);
  return null;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {string} originKey
 * @param {import("./types.js").AnimalCell} originCell
 * @param {number} radius
 * @returns {number}
 */
function countSameSpeciesInRadius(state, originKey, originCell, radius) {
  const m = originKey.match(/^(\d+)_(\d+)$/);
  if (!m) return 0;
  const ox = Number(m[1]);
  const oy = Number(m[2]);
  const cells = state.grid.cells;
  let count = 0;
  for (const [k, c] of Object.entries(cells)) {
    if (countSameSpeciesCell({ k, c, originCell, ox, oy, radius })) count += 1;
  }
  return count;
}

/**
 * False if animal should die from solitude (no same species in radius for long enough).
 * @param {import("./types.js").GameState} state
 * @param {{ originKey: string, originCell: import("./types.js").AnimalCell, nowUnix: number }} opts
 * @returns {boolean}
 */
function checkNotAlone(state, opts) {
  const { originKey, originCell, nowUnix } = opts;
  const cfg = GameConfig.Animal;
  if (!cfg || cfg.MinSameSpeciesInRadius === null || cfg.MinSameSpeciesInRadius === undefined || cfg.MinSameSpeciesInRadius <= 0) return true;
  const maxAlone = cfg.MaxSecondsAlone ?? 300;
  const radius = cfg.RadiusCells ?? 5;
  const placedAt = originCell.placedAt ?? nowUnix;
  if (nowUnix - placedAt < maxAlone) return true;
  const minSame = cfg.MinSameSpeciesInRadius ?? 1;
  return countSameSpeciesInRadius(state, originKey, originCell, radius) >= minSame;
}

/**
 * @param {{ k: string, c: import("./types.js").Cell, originCell: import("./types.js").AnimalCell, ox: number, oy: number, radius: number }} opts
 * @returns {boolean}
 */
function countSameSpeciesCell(opts) {
  const { k, c, originCell, ox, oy, radius } = opts;
  if (c === null || c === undefined || c.kind !== "animal" || c.id !== originCell.id) return false;
  if (!isOriginCell(k, c)) return false;
  const km = k.match(/^(\d+)_(\d+)$/);
  if (!km) return false;
  const kx = Number(km[1]);
  const ky = Number(km[2]);
  if (kx === ox && ky === oy) return false;
  const manhattan = Math.abs(kx - ox) + Math.abs(ky - oy);
  return manhattan <= radius;
}

function getBlockOrigin(opts) {
  const m = opts.key.match(/^(\d+)_(\d+)$/);
  if (!m) return null;
  return { ox: Number(m[1]), oy: Number(m[2]) };
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @param {number} maxMatureNotPlaced
 * @returns {number}
 */
function filterPendingBabies(state, nowUnix, maxMatureNotPlaced) {
  const pendingBabies = state.pendingBabies ?? [];
  let removed = 0;
  state.pendingBabies = pendingBabies.filter((p) => {
    if (nowUnix <= p.readyAt) return true;
    if (nowUnix - p.readyAt >= maxMatureNotPlaced) {
      removed += 1;
      return false;
    }
    return true;
  });
  return removed;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @param {number} maxReadyNotPlaced
 * @returns {number}
 */
function filterReceptionAnimals(state, nowUnix, maxReadyNotPlaced) {
  const receptionAnimals = state.receptionAnimals ?? [];
  let removed = 0;
  state.receptionAnimals = receptionAnimals.filter((r) => {
    if (nowUnix <= r.readyAt) return true;
    if (nowUnix - r.readyAt >= maxReadyNotPlaced) {
      removed += 1;
      return false;
    }
    return true;
  });
  return removed;
}
