import { cellKey, withinBounds, getBlockKeys } from "./grid-utils.js";
import {
  getNurseryBuildCost,
  getNurseryUpgradeCost,
  getSouvenirShopBuildCost,
  getSouvenirShopUpgradeCost,
  getBuildingBuildCost,
  getBuildingUpgradeCost,
  getBuildingMaxLevel,
  BUILDING_KINDS,
} from "./economy.js";
import { GameConfig } from "./config.js";

/**
 * Origin and dimensions for an animal cell (possibly from originKey). Returns null if not animal.
 * @param {import("./types.js").GameState} state
 * @param {import("./types.js").Cell} cell
 * @param {string} key
 * @param {{ x: number, y: number }} pos
 * @returns {{ ox: number, oy: number, w: number, h: number } | null}
 */
function getAnimalBlockOrigin(state, cell, key, pos) {
  if (cell === null || cell === undefined || cell.kind !== "animal") return null;
  let ox = pos.x;
  let oy = pos.y;
  let w = cell.cellsWide ?? 1;
  let h = cell.cellsHigh ?? 1;
  if (cell.originKey !== null && cell.originKey !== undefined) {
    const m = cell.originKey.match(/^(\d+)_(\d+)$/);
    if (m) {
      ox = Number(m[1]);
      oy = Number(m[2]);
      const origin = state.grid.cells[cell.originKey];
      if (origin && origin.kind === "animal") {
        w = origin.cellsWide ?? 1;
        h = origin.cellsHigh ?? 1;
      }
    }
  }
  return { ox, oy, w, h };
}

/**
 * All keys that belong to the same animal block as the cell at (x, y). If not an animal or single-cell, returns [cellKey(x,y)].
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {string[]}
 */
export function getBlockKeysFromCell(state, x, y) {
  const key = cellKey(x, y);
  const cell = state.grid.cells[key];
  const origin = getAnimalBlockOrigin(state, cell, key, { x, y });
  if (origin === null) return [key];
  return getBlockKeys(origin.ox, origin.oy, origin.w, origin.h);
}

/**
 * Build set of cell keys to exclude when placing (block at excludeOriginKey).
 * @param {import("./types.js").GameState} state
 * @param {string} [excludeOriginKey]
 * @returns {Set<string>}
 */
function buildExcludeSet(state, excludeOriginKey) {
  const excludeSet = new Set();
  if (excludeOriginKey === null || excludeOriginKey === undefined) return excludeSet;
  const orig = state.grid.cells[excludeOriginKey];
  if (orig && orig.kind === "animal") {
    const [ox, oy] = excludeOriginKey.split("_").map(Number);
    const ow = orig.cellsWide ?? 1;
    const oh = orig.cellsHigh ?? 1;
    getBlockKeys(ox, oy, ow, oh).forEach((k) => excludeSet.add(k));
  }
  return excludeSet;
}

/**
 * Check if a rectangular block can be placed (all cells empty and in bounds). Optionally exclude keys that belong to excludeOriginKey block.
 * @param {import("./types.js").GameState} state
 * @param {{ originX: number, originY: number, w: number, h: number, excludeOriginKey?: string }} opts
 * @returns {[boolean, string?]}
 */
export function canPlaceMultiCell(state, opts) {
  const { originX, originY, w, h, excludeOriginKey } = opts;
  const excludeSet = buildExcludeSet(state, excludeOriginKey);
  for (let dy = 0; dy < h; dy++) {
    for (let dx = 0; dx < w; dx++) {
      const nx = originX + dx;
      const ny = originY + dy;
      if (!withinBounds(state.grid.width, state.grid.height, nx, ny)) return [false, "OutOfBounds"];
      const k = cellKey(nx, ny);
      if (!excludeSet.has(k)) {
        const c = state.grid.cells[k];
        if (c !== null && c !== undefined) return [false, "Occupied"];
      }
    }
  }
  return [true, undefined];
}

/**
 * Fill a block with the same animal data (originKey set to origin, each cell gets full copy).
 * @param {import("./types.js").GameState} state
 * @param {number} originX
 * @param {number} originY
 * @param {import("./types.js").AnimalCell} animalData
 */
export function fillAnimalBlock(state, originX, originY, animalData) {
  const w = animalData.cellsWide ?? 1;
  const h = animalData.cellsHigh ?? 1;
  const originKey = cellKey(originX, originY);
  const data = { ...animalData, originKey };
  for (const k of getBlockKeys(originX, originY, w, h)) {
    state.grid.cells[k] = { ...data };
  }
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {[boolean, string?]}
 */
export function canPlace(state, x, y) {
  if (!withinBounds(state.grid.width, state.grid.height, x, y)) return [false, "OutOfBounds"];
  const key = cellKey(x, y);
  if (state.grid.cells[key] !== null && state.grid.cells[key] !== undefined) return [false, "Occupied"];
  return [true, undefined];
}

/**
 * @param {import("./types.js").GameState} state
 * @param {{ eggType: string, tokenId: number, x: number, y: number, hatchAt: number, seed: number }} opts
 * @returns {[boolean, string?]}
 */
export function placeEgg(state, opts) {
  const { eggType, tokenId, x, y, hatchAt, seed } = opts;
  const [ok, reason] = canPlace(state, x, y);
  if (!ok) return [false, reason];
  const key = cellKey(x, y);
  state.grid.cells[key] = {
    kind: "egg",
    eggType,
    tokenId,
    hatchAt,
    seed,
  };
  return [true, undefined];
}

/**
 * Move an animal block from (ox,oy) to (toX, toY). Caller ensures source is animal and keys differ.
 * @param {import("./types.js").GameState} state
 * @param {{ blockKeys: string[], ox: number, oy: number, toX: number, toY: number, source: import("./types.js").AnimalCell }} opts
 * @returns {[boolean, string?]}
 */
function moveAnimalBlock(state, opts) {
  const { blockKeys, ox, oy, toX, toY, source } = opts;
  const w = source.cellsWide ?? 1;
  const h = source.cellsHigh ?? 1;
  const originKey = cellKey(ox, oy);
  const [ok, reason] = canPlaceMultiCell(state, { originX: toX, originY: toY, w, h, excludeOriginKey: originKey });
  if (!ok) return [false, reason];
  const animalData = { ...source, originKey: cellKey(toX, toY), cellsWide: w, cellsHigh: h };
  for (const k of blockKeys) delete state.grid.cells[k];
  fillAnimalBlock(state, toX, toY, animalData);
  return [true, undefined];
}

/**
 * Déplace le contenu d'une case vers une case vide (œuf ou animal). For multi-cell animals, moves the whole block.
 * @param {import("./types.js").GameState} state
 * @param {{ fromX: number, fromY: number, toX: number, toY: number }} opts
 * @returns {[boolean, string?]}
 */
export function moveCell(state, opts) {
  const { fromX, fromY, toX, toY } = opts;
  const fromKey = cellKey(fromX, fromY);
  const toKey = cellKey(toX, toY);
  if (fromKey === toKey) return [false, "SameCell"];
  const source = state.grid.cells[fromKey];
  if (source === null || source === undefined) return [false, "NoSource"];
  if (source.kind === "animal") {
    const blockKeys = getBlockKeysFromCell(state, fromX, fromY);
    const origin = getAnimalBlockOrigin(state, source, fromKey, { x: fromX, y: fromY });
    if (origin === null) return [false, "NoSource"];
    return moveAnimalBlock(state, { blockKeys, ox: origin.ox, oy: origin.oy, toX, toY, source });
  }
  const [ok, reason] = canPlace(state, toX, toY);
  if (!ok) return [false, reason];
  state.grid.cells[toKey] = source;
  delete state.grid.cells[fromKey];
  return [true, undefined];
}

/**
 * Set an empty cell to nursery. Cell must be empty. Costs coins.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {[boolean, string?]}
 */
export function tryBuildNursery(state, x, y) {
  const [ok, reason] = canPlace(state, x, y);
  if (!ok) return [ok, reason];
  const cost = getNurseryBuildCost();
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  state.grid.cells[cellKey(x, y)] = { kind: "nursery", level: 1 };
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

/**
 * Set an empty cell to souvenir shop. Cell must be empty. Costs coins.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {[boolean, string?]}
 */
export function tryBuildSouvenirShop(state, x, y) {
  const [ok, reason] = canPlace(state, x, y);
  if (!ok) return [ok, reason];
  const cost = getSouvenirShopBuildCost();
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  state.grid.cells[cellKey(x, y)] = { kind: "souvenirShop", level: 1 };
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

/**
 * Upgrade a nursery cell. Cell must be nursery and below max level.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {[boolean, string?]}
 */
export function tryUpgradeNursery(state, x, y) {
  const key = cellKey(x, y);
  const cell = state.grid.cells[key];
  if (cell === null || cell === undefined || cell.kind !== "nursery") return [false, "NotNursery"];
  const maxLevel = GameConfig.Nursery?.MaxLevel ?? 5;
  const level = cell.level ?? 1;
  if (level >= maxLevel) return [false, "NurseryMaxLevel"];
  const cost = getNurseryUpgradeCost(level);
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  cell.level = level + 1;
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

/**
 * Upgrade a souvenir shop cell. Cell must be souvenirShop and below max level.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @returns {[boolean, string?]}
 */
export function tryUpgradeSouvenirShop(state, x, y) {
  const key = cellKey(x, y);
  const cell = state.grid.cells[key];
  if (cell === null || cell === undefined || cell.kind !== "souvenirShop") return [false, "NotSouvenirShop"];
  const maxLevel = GameConfig.SouvenirShop?.MaxLevel ?? 5;
  const level = cell.level ?? 1;
  if (level >= maxLevel) return [false, "SouvenirShopMaxLevel"];
  const cost = getSouvenirShopUpgradeCost(level);
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  cell.level = level + 1;
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

/**
 * Build a building of the given kind on an empty cell. Used by research, billeterie, food, reception, biomeChangeColor, biomeChangeTemp.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @param {typeof BUILDING_KINDS[number]} kind
 * @returns {[boolean, string?]}
 */
export function tryBuildBuilding(state, x, y, kind) {
  if (!BUILDING_KINDS.includes(kind)) return [false, "UnknownBuilding"];
  const [ok, reason] = canPlace(state, x, y);
  if (!ok) return [ok, reason];
  const cost = getBuildingBuildCost(kind);
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  state.grid.cells[cellKey(x, y)] = { kind, level: 1 };
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

/**
 * Upgrade a building cell of the given kind. Cell must be that kind and below max level.
 * @param {import("./types.js").GameState} state
 * @param {number} x
 * @param {number} y
 * @param {typeof BUILDING_KINDS[number]} kind
 * @returns {[boolean, string?]}
 */
export function tryUpgradeBuilding(state, x, y, kind) {
  if (!BUILDING_KINDS.includes(kind)) return [false, "UnknownBuilding"];
  const key = cellKey(x, y);
  const cell = state.grid.cells[key];
  if (cell === null || cell === undefined || cell.kind !== kind) return [false, `Not${kind.charAt(0).toUpperCase() + kind.slice(1)}`];
  const maxLevel = getBuildingMaxLevel(kind);
  const level = cell.level ?? 1;
  if (level >= maxLevel) return [false, `${kind.charAt(0).toUpperCase() + kind.slice(1)}MaxLevel`];
  const cost = getBuildingUpgradeCost(kind, level);
  if (state.coins < cost) return [false, "NotEnoughCoins"];
  state.coins -= cost;
  cell.level = level + 1;
  state.lastEvolutionAt = Math.floor(Date.now() / 1000);
  return [true, undefined];
}

export function tryBuildResearch(state, x, y) {
  return tryBuildBuilding(state, x, y, "research");
}
export function tryUpgradeResearch(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "research");
}
export function tryBuildBilleterie(state, x, y) {
  return tryBuildBuilding(state, x, y, "billeterie");
}
export function tryUpgradeBilleterie(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "billeterie");
}
export function tryBuildFood(state, x, y) {
  return tryBuildBuilding(state, x, y, "food");
}
export function tryUpgradeFood(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "food");
}
export function tryBuildReception(state, x, y) {
  return tryBuildBuilding(state, x, y, "reception");
}
export function tryUpgradeReception(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "reception");
}
export function tryBuildBiomeChangeColor(state, x, y) {
  return tryBuildBuilding(state, x, y, "biomeChangeColor");
}
export function tryUpgradeBiomeChangeColor(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "biomeChangeColor");
}
export function tryBuildBiomeChangeTemp(state, x, y) {
  return tryBuildBuilding(state, x, y, "biomeChangeTemp");
}
export function tryUpgradeBiomeChangeTemp(state, x, y) {
  return tryUpgradeBuilding(state, x, y, "biomeChangeTemp");
}
