/**
 * 電力系統。
 *
 * 機械を動かすには2つ揃っている必要がある。
 *   1. 送電 — 配電盤の届く範囲にあること（空間の問題）
 *   2. 発電 — 系統契約と自家発電の合計が需要を上回っていること（容量の問題）
 *
 * 容量が足りないと全機械が一律に減速し（ブラウンアウト）、やがて停電事故になる。
 */
import { GENERATORS, GRID_CONTRACT, POLE_RADIUS, tilePowerDraw } from '../core/config';
import { idx, tx, ty } from '../core/grid';
import type { GameState } from './state';

export interface PowerReport {
  /** 配電盤の範囲内にある機械の消費電力の合計 */
  demand: number;
  /** 系統契約 + 自家発電の合計 */
  supply: number;
  /** 系統契約ぶん */
  grid: number;
  /** 自家発電ぶん */
  onsite: number;
  /** 供給が需要に対して足りている割合 (1で充足) */
  ratio: number;
  /** 配電盤の範囲外にあって動かせない機械の数 */
  unpowered: number;
}

/** 系統から受電できる契約電力。開発と対策で増やせる。 */
export function gridContract(s: GameState): number {
  return (
    GRID_CONTRACT +
    (s.techs.includes('power_grid') ? 8 : 0) +
    (s.upgrades['power_margin'] ?? 0) * 6
  );
}

/** 配電盤の覆う範囲か (チェビシェフ距離) */
function coveredBy(index: number, poleIndex: number): boolean {
  const dx = Math.abs(tx(index) - tx(poleIndex));
  const dy = Math.abs(ty(index) - ty(poleIndex));
  return Math.max(dx, dy) <= POLE_RADIUS;
}

/** その日の発電設備の出力を確定させる (太陽光は天候で振れる)。 */
export function rollWeather(s: GameState): void {
  // 0.55〜1.0 の間で日射量が決まる
  s.solarFactor = s.rng.range(0.55, 1);
}

/** 配電盤の初期設置 */
export function placeStarterPole(s: GameState): void {
  const spot = idx(3, 4);
  if (s.tiles[spot].kind === 'floor') s.tiles[spot].kind = 'pole';
}

/** いま電力が届いているマスか。配電盤自身と発電設備は常に生きている。 */
export function isPowered(s: GameState, index: number): boolean {
  const t = s.tiles[index];
  if (t.kind === 'pole' || t.kind === 'generator' || t.kind === 'solar') return true;
  for (let i = 0; i < s.tiles.length; i++) {
    if (s.tiles[i].kind !== 'pole') continue;
    if (coveredBy(index, i)) return true;
  }
  return false;
}

/** 電力の需給を集計する。 */
export function computePower(s: GameState): PowerReport {
  const poles: number[] = [];
  for (let i = 0; i < s.tiles.length; i++) {
    if (s.tiles[i].kind === 'pole') poles.push(i);
  }

  let demand = 0;
  let unpowered = 0;
  for (let i = 0; i < s.tiles.length; i++) {
    const draw = tilePowerDraw(s.tiles[i].kind);
    if (draw <= 0) continue;

    if (poles.some((p) => coveredBy(i, p))) demand += draw;
    else unpowered++;
  }

  let onsite = 0;
  for (const t of s.tiles) {
    const gen = GENERATORS[t.kind];
    if (!gen) continue;
    onsite += gen.output * (gen.weatherDependent ? s.solarFactor : 1);
  }

  const grid = gridContract(s);
  const supply = grid + onsite;
  return {
    demand,
    supply,
    grid,
    onsite,
    ratio: demand <= 0 ? 1 : Math.min(1, supply / demand),
    unpowered,
  };
}

/**
 * 機械が動ける速度の倍率。
 * 配電盤の範囲外なら0（＝止まる）、容量不足なら全体が一律に落ちる。
 */
export function powerFactor(s: GameState, index: number): number {
  if (!s.powerCoverage[index]) return 0;
  return s.powerRatio;
}

/** シフト中に一度だけ需給を計算し直し、結果をキャッシュする。 */
export function refreshPower(s: GameState): void {
  const report = computePower(s);
  s.powerRatio = report.ratio;
  s.powerDemand = report.demand;
  s.powerSupply = report.supply;

  const coverage = new Uint8Array(s.tiles.length);
  const poles: number[] = [];
  for (let i = 0; i < s.tiles.length; i++) {
    if (s.tiles[i].kind === 'pole') poles.push(i);
  }
  for (let i = 0; i < s.tiles.length; i++) {
    const t = s.tiles[i];
    if (t.kind === 'pole' || t.kind === 'generator' || t.kind === 'solar') {
      coverage[i] = 1;
      continue;
    }
    coverage[i] = poles.some((p) => coveredBy(i, p)) ? 1 : 0;
  }
  s.powerCoverage = coverage;
}
