import { LootTables } from "./loot-tables.js";
import { getIncomeMultiplier } from "./mutation-rules.js";
import { getLevelMultiplier } from "./economy.js";
import { GameConfig } from "./config.js";
import { getPrestigeIncomeMultiplier } from "./prestige.js";
import { isOriginCell } from "./grid-utils.js";
import { getCurrentSeason, getSeasonVisitorMultiplier, getSeasonTicketPriceMultiplier } from "./seasons.js";
import { getTotalAnimalValue } from "./income-value.js";
import { getAttractivityBase } from "./income-attractivity.js";

/**
 * Visitor demand multiplier by time of day (spec visiteur: 08h-10h faible, 10h-16h fort, 16h-18h décroissant, >18h nul).
 * @param {number} timeOfDay 0..24
 * @returns {number}
 */
function getVisitorDemandHourMultiplier(timeOfDay) {
  const t = timeOfDay % 24;
  if (t < 8 || t >= 20) return 0;
  if (t >= 8 && t < 10) return 0.5;
  if (t >= 10 && t < 16) return 1;
  if (t >= 16 && t < 18) return 0.7;
  return 0.3;
}

/**
 * Max simultaneous visitors allowed by billeterie capacity. Entry is only via billeterie.
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
export function getBilleterieCapacity(state) {
  const cfg = GameConfig.Billeterie;
  if (!cfg) return 0;
  const unit = cfg.VisitorsPerUnit ?? 20;
  let total = 0;
  for (const cell of Object.values(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "billeterie") {
      total += (cell.level ?? 1) * unit;
    }
  }
  return total;
}

/**
 * Attraction from cities: per-city contribution = min(maxVisitorsTowardZoos, rawWeight * 100), summed then scaled.
 * Closer cities contribute more, but each city is capped by maxVisitorsTowardZoos.
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getCityAttraction(state) {
  const cities = GameConfig.WorldMap?.Cities;
  if (!cities || cities.length === 0) return 0;
  const zoos = state.worldZoos ?? [];
  const player = zoos.find((z) => z.id === "player");
  if (!player) return 0;
  const scale = GameConfig.Visitor.CityAttractionScale ?? 0.002;
  const rawMultiplier = 100;
  let sum = 0;
  for (const city of cities) {
    const dx = (city.x - player.x) / 100;
    const dy = (city.y - player.y) / 100;
    const dist = Math.sqrt(dx * dx + dy * dy) || 0.01;
    const raw = 1 / (1 + dist);
    const maxFromCity = city.maxVisitorsTowardZoos ?? 999;
    const contrib = Math.min(maxFromCity, raw * rawMultiplier);
    sum += contrib;
  }
  return sum * scale;
}

/**
 * Decay multiplier when the zoo has not evolved (upgrade/place/sell) for a while.
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @returns {number}
 */
function getStagnationMultiplier(state, nowUnix) {
  const after = GameConfig.Visitor.StagnationDecayAfterSeconds ?? 60;
  const perMin = GameConfig.Visitor.StagnationDecayPerMinute ?? 0.05;
  const last = state.lastEvolutionAt ?? 0;
  const elapsed = Math.max(0, nowUnix - last);
  if (elapsed <= after) return 1;
  const minutesStagnant = (elapsed - after) / 60;
  const decay = Math.min(0.9, minutesStagnant * perMin);
  return Math.max(0.1, 1 - decay);
}

/**
 * Shop bonus component of stay multiplier (souvenir shops).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getStayMultiplierShopBonus(state) {
  let shopBonus = 0;
  for (const cell of Object.values(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "souvenirShop") {
      shopBonus += (cell.level ?? 1) * (GameConfig.Visitor.StayMultiplierPerShopLevel ?? 0.15);
    }
  }
  return shopBonus;
}

/**
 * Diversity bonus component (species count).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getStayMultiplierDiversityBonus(state) {
  const speciesSet = new Set();
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "animal" && isOriginCell(key, cell)) speciesSet.add(cell.id);
  }
  return speciesSet.size * (GameConfig.Visitor.StayMultiplierPerSpecies ?? 0.02);
}

/**
 * Stay duration multiplier from boutiques and animal diversity (visitors stay longer).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getStayMultiplier(state) {
  const shopBonus = getStayMultiplierShopBonus(state);
  const diversityBonus = getStayMultiplierDiversityBonus(state);
  return Math.max(0.5, 1 + shopBonus + diversityBonus);
}

/**
 * Stay duration in seconds (base 1 day × stay multiplier). Visitors leave when now > arrivedAt + this.
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getStayDurationSeconds(state) {
  const base = GameConfig.Time?.DayLengthSeconds ?? 120;
  return base * getStayMultiplier(state);
}

/**
 * Demand for visitors (before billeterie cap).
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @returns {number}
 */
function getVisitorDemand(state, nowUnix) {
  let animalCount = 0;
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell.kind === "animal" && isOriginCell(key, cell)) animalCount += 1;
  }
  const visitorsPerAnimal = GameConfig.Visitor.VisitorsPerAnimal;
  const plotBonus = (state.plotLevel ?? 1) * GameConfig.Visitor.PlotLevelBonus;
  let demand = Math.floor(animalCount * visitorsPerAnimal + plotBonus);
  const cityAttraction = getCityAttraction(state);
  const animalValue = getTotalAnimalValue(state);
  const animalValueScale = GameConfig.Visitor.AnimalValueScale ?? 0.00015;
  demand *= 1 + cityAttraction;
  demand *= 1 + animalValue * animalValueScale;
  demand *= getStagnationMultiplier(state, nowUnix);
  const seasonMult = getSeasonVisitorMultiplier(getCurrentSeason(state));
  demand *= seasonMult;
  const hourMult = getVisitorDemandHourMultiplier(state.timeOfDay ?? 6);
  demand *= hourMult;
  return Math.max(0, Math.floor(demand));
}

/**
 * Remove visitors who exceeded stay duration.
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 */
function filterExpiredVisitors(state, nowUnix) {
  const stayDuration = getStayDurationSeconds(state);
  state.visitorArrivals = (state.visitorArrivals ?? []).filter(
    (v) => nowUnix < v.arrivedAt + stayDuration
  );
}

/**
 * Whether we are within opening hours for new entries.
 * @param {import("./types.js").GameState} state
 * @returns {boolean}
 */
function isVisitorOpeningHours(state) {
  const timeOfDay = state.timeOfDay ?? 6;
  const openHour = GameConfig.Billeterie?.OpenHour ?? 8;
  const closeHour = GameConfig.Billeterie?.CloseHour ?? 20;
  return timeOfDay >= openHour && timeOfDay < closeHour;
}

/**
 * Update visitor entities: remove those who exceeded stay duration, add new arrivals up to min(cap, demand).
 * New arrivals only during opening hours (OpenHour–CloseHour). Max MaxEntryPerSecond new visitors per second.
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 */
export function tickVisitorArrivals(state, nowUnix) {
  state.visitorArrivals = state.visitorArrivals ?? [];
  filterExpiredVisitors(state, nowUnix);
  if (!isVisitorOpeningHours(state)) return;
  const demand = getVisitorDemand(state, nowUnix);
  const cap = getBilleterieCapacity(state);
  const target = Math.min(cap, demand);
  const current = state.visitorArrivals.length;
  const maxToAdd = target - current;
  if (maxToAdd <= 0) return;
  const maxPerSecond = GameConfig.Billeterie?.MaxEntryPerSecond ?? 1;
  const secondsPerTick = GameConfig.IncomeTickMs / 1000;
  const maxThisTick = Math.min(maxToAdd, Math.ceil(maxPerSecond * secondsPerTick));
  for (let i = 0; i < maxThisTick; i++) {
    state.visitorArrivals.push({ arrivedAt: nowUnix });
  }
}

/**
 * Raw visitor count from animals and plot when no billeterie (for fallback).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getVisitorCountFallback(state) {
  let animalCount = 0;
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell.kind === "animal" && isOriginCell(key, cell)) animalCount += 1;
  }
  const visitorsPerAnimal = GameConfig.Visitor.VisitorsPerAnimal;
  const plotBonus = (state.plotLevel ?? 1) * GameConfig.Visitor.PlotLevelBonus;
  return Math.max(0, Math.floor(animalCount * visitorsPerAnimal + plotBonus));
}

/**
 * Visitor count capped by billeterie.
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getVisitorCountCapped(state) {
  const arrivals = state.visitorArrivals ?? [];
  let visitorCount = arrivals.length;
  if (visitorCount === 0 && getBilleterieCapacity(state) === 0) {
    visitorCount = getVisitorCountFallback(state);
  }
  const billeterieCap = getBilleterieCapacity(state);
  if (billeterieCap > 0 && visitorCount > billeterieCap) visitorCount = billeterieCap;
  return visitorCount;
}

/**
 * Luxury shop multiplier component for souvenir bonus (>= 1).
 * @returns {number}
 */
function getLuxuryShopMultiplier() {
  const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
  const luxuryShopMult = GameConfig.Visitor.LuxuryShopMultiplier ?? 1;
  if (luxuryChance > 0 && luxuryShopMult > 1) {
    return 1 + luxuryChance * (luxuryShopMult - 1);
  }
  return 1;
}

/**
 * Souvenir shop bonus multiplier (>= 1).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getSouvenirBonus(state) {
  let shopCount = 0;
  for (const cell of Object.values(state.grid.cells)) {
    if (cell && cell.kind === "souvenirShop") shopCount += (cell.level ?? 1);
  }
  if (shopCount === 0) return 1;
  const bonusPerShop = GameConfig.Visitor.SouvenirShopBonusPerShop ?? 0.2;
  return (1 + shopCount * bonusPerShop) * getLuxuryShopMultiplier();
}

/**
 * Luxury entry multiplier (>= 1).
 * @returns {number}
 */
function getLuxuryEntryMultiplier() {
  const luxuryChance = GameConfig.Visitor.LuxuryGuestChance ?? 0;
  const luxuryEntryMult = GameConfig.Visitor.LuxuryEntryMultiplier ?? 1;
  if (luxuryChance > 0 && luxuryEntryMult > 1) {
    return 1 + luxuryChance * (luxuryEntryMult - 1);
  }
  return 1;
}

/**
 * Payment per visitor (base × souvenir × luxury × season).
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
function getPaymentPerVisitor(state) {
  let paymentPerVisitor = GameConfig.Visitor.BasePaymentPerVisitor;
  paymentPerVisitor *= getSouvenirBonus(state);
  paymentPerVisitor *= getLuxuryEntryMultiplier();
  const ticketSeasonMult = getSeasonTicketPriceMultiplier(getCurrentSeason(state));
  paymentPerVisitor *= ticketSeasonMult;
  return paymentPerVisitor;
}

/**
 * Visitor count and average payment per visitor per second. Includes luxury guest effect (LuxuryGuestChance, LuxuryEntryMultiplier, LuxuryShopMultiplier) in the average.
 * @param {import("./types.js").GameState} state
 * @returns {{ visitorCount: number, paymentPerVisitor: number }}
 */
function getVisitorParams(state) {
  const visitorCount = getVisitorCountCapped(state);
  const paymentPerVisitor = getPaymentPerVisitor(state);
  return { visitorCount, paymentPerVisitor };
}

export function getVisitorCount(state) {
  return getVisitorParams(state).visitorCount;
}

/**
 * Attractivity score for display and future city allocation. Formula: value + species + rarity + fill rate, minus death penalty, plus birth bonus.
 * @param {import("./types.js").GameState} state
 * @returns {number}
 */
export function getAttractivityScore(state) {
  const { valueNorm, speciesNorm, rarityNorm, fillNorm } = getAttractivityBase(state);
  let score = valueNorm + speciesNorm + rarityNorm + fillNorm;
  const deathPenalty = GameConfig.Visitor?.AttractivityDeathPenalty ?? 0.5;
  const birthBonus = GameConfig.Visitor?.AttractivityBirthBonus ?? 0.2;
  const deaths = state.deathCountRecent ?? 0;
  const births = state.birthCount ?? 0;
  score -= deathPenalty * deaths;
  score += birthBonus * births;
  const incidentBonus = state.attractivityBonusFromIncidents ?? 0;
  score += incidentBonus;
  return Math.max(0, score);
}

/**
 * @param {import("./types.js").AnimalCell} cell
 * @returns {number}
 */
function incomePerSecond(cell) {
  const animalDef = LootTables.Animals[cell.id];
  if (animalDef === null || animalDef === undefined) throw new Error("IncomeService: unknown animal");
  const mutationMult = getIncomeMultiplier(cell.mutation);
  const levelMult = getLevelMultiplier(cell.level);
  return animalDef.baseIncomePerSecond * mutationMult * levelMult;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} dt
 * @param {{ incomeMultiplier: number }} eventModifiers
 * @returns {{ animal: number, visitor: number }}
 */
export function tick(state, dt, eventModifiers) {
  const prestigeMult = getPrestigeIncomeMultiplier(state.prestigeLevel);
  let animalTotal = 0;
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell.kind === "animal" && isOriginCell(key, cell))
      animalTotal += incomePerSecond(cell) * dt * eventModifiers.incomeMultiplier * prestigeMult;
  }
  const { visitorCount, paymentPerVisitor } = getVisitorParams(state);
  const visitorTotal = visitorCount * paymentPerVisitor * dt * prestigeMult;
  const total = animalTotal + visitorTotal;
  state.coins += total;
  if (state.stats) state.stats.coinsEarned = (state.stats.coinsEarned ?? 0) + total;
  return { animal: animalTotal, visitor: visitorTotal };
}
