import { GameConfig } from "./config.js";
import { refreshOffers, shouldRefresh } from "./conveyor.js";
import { run as runHatching } from "./hatching.js";
import { tick as incomeTick, tickVisitorArrivals, getAttractivityScore } from "./income.js";
import { getActiveModifiers } from "./event-service.js";
import { tickTime, tickWeather } from "./time-weather.js";
import { tickQuests } from "./quests.js";
import { saveState } from "./state.js";
import { playSound } from "./audio.js";
import { pruneTruckSales, addNpcTruckSale, shouldAddNpcTruck, tickLaboratory } from "./world-map.js";
import { tickAnimalVisits } from "./animal-visits.js";
import { tickPlayerAutoMode } from "./bot-zoo.js";
import { tickFeeding, checkDeathCauses, getFeedingRate } from "./food.js";
import { tickReproduction, getReproductionScore } from "./reproduction.js";
import { tickVisitorIncidents } from "./visitor-incidents.js";
import { tickSaleListings } from "./trade.js";
import { getCurrentSeason } from "./seasons.js";

/**
 * @param {import("./types.js").GameState} state
 * @param {number} pointsPerLevelPerSecond
 * @param {number} dt
 * @returns {number}
 */
function sumResearchPointsFromCells(state, pointsPerLevelPerSecond, dt) {
  let total = 0;
  for (const [, cell] of Object.entries(state.grid.cells)) {
    if (cell !== null && cell !== undefined && cell.kind === "research") {
      const level = cell.level ?? 1;
      total += pointsPerLevelPerSecond * level * dt;
    }
  }
  return total;
}

/**
 * Add research points from all research cells. PointsPerTickPerLevel * level per second.
 * @param {import("./types.js").GameState} state
 * @param {number} dt
 * @returns {void}
 */
function tickResearch(state, dt) {
  const cfg = GameConfig.Research;
  if (!cfg || cfg.PointsPerTickPerLevel === null || cfg.PointsPerTickPerLevel === undefined) return;
  const total = sumResearchPointsFromCells(state, cfg.PointsPerTickPerLevel, dt);
  if (total > 0) state.researchPoints = (state.researchPoints ?? 0) + total;
}

/**
 * Run one simulation tick: time, feeding, reproduction, visitors, income, research, hatching, quests.
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @param {number} nowMs
 * @param {number} dt
 * @returns {{ hatched: Array<{ x: number, y: number }>, questEarned: number }}
 */
function doOneTick(state, nowUnix, nowMs, dt) {
  if (shouldRefresh(state, nowUnix)) refreshOffers(state, nowUnix);
  pruneTruckSales(state, nowMs);
  const eventModifiers = getActiveModifiers(nowUnix);
  tickTime(state, dt);
  tickWeather(state, nowUnix);
  tickAnimalVisits(state, nowUnix, nowMs);
  if (state.autoMode) tickPlayerAutoMode(state, nowUnix);
  tickFeeding(state, nowUnix);
  state.feedingRate = getFeedingRate(state, nowUnix);
    checkDeathCauses(state, nowUnix);
    tickReproduction(state, nowUnix);
    state.reproductionScore = getReproductionScore(state);
    tickSaleListings(state, nowUnix);
    tickVisitorArrivals(state, nowUnix);
    tickVisitorIncidents(state, nowUnix);
  incomeTick(state, dt, eventModifiers);
  tickResearch(state, dt);
  state.attractivityScore = getAttractivityScore(state);
  const { hatched } = runHatching(state, nowUnix, eventModifiers);
  const questEarned = tickQuests(state);
  return { hatched, questEarned };
}

/**
 * @param {() => import("./types.js").GameState} getState
 * @param {(state: import("./types.js").GameState, payload: { lastHatched?: Array<{ x: number, y: number }> }) => void} onUpdate
 * @param {(state: import("./types.js").GameState) => void} [saveStateFn]
 * @returns {void}
 */
export function startGameLoop(getState, onUpdate, saveStateFn) {
  const save = saveStateFn || saveState;
  let lastWall = performance.now() / 1000;
  let saveAccum = 0;
  let lastNpcTruckAt = 0;

  function loop() {
    const state = getState();
    const nowWall = performance.now() / 1000;
    const dt = Math.min(nowWall - lastWall, 2);
    lastWall = nowWall;
    const nowUnix = Math.floor(Date.now() / 1000);
    const nowMs = Date.now();

    if (shouldAddNpcTruck(nowMs, lastNpcTruckAt)) {
      addNpcTruckSale(state, nowMs);
      lastNpcTruckAt = nowMs;
    }
    tickLaboratory(state, nowUnix);
  const { hatched, questEarned } = doOneTick(state, nowUnix, nowMs, dt);
  const newSeason = getCurrentSeason(state);
  if (state.lastSeason !== undefined && state.lastSeason !== newSeason) {
    state.seasonChangeMessage = newSeason;
  }
  state.lastSeason = newSeason;
  if (questEarned > 0) playSound("quest");
    onUpdate(state, { lastHatched: hatched });

    saveAccum += dt;
    if (saveAccum >= GameConfig.SaveIntervalMs / 1000) {
      saveAccum = 0;
      save(state);
    }
  }

  const intervalMs = Math.max(100, GameConfig.IncomeTickMs);
  setInterval(loop, intervalMs);
  loop();
}
