import { STRUCT, ZONE, BUILDINGS, START_MONEY } from '../config.js';
import { lineTiles } from '../utils.js';

/**
 * AutoBuilder — a SimCity-style advisor that spends one "planning pass"
 * per call, always keeping an emergency reserve. Priorities:
 *   1. Power headroom (blackouts stall everything)
 *   2. Road access for zoned land (nothing develops without a road)
 *   3. New districts when demand is high and empty zone plots run out
 *   4. Missing service coverage over developed tiles
 *   5. Leisure to push land value over upgrade thresholds
 */
export class AutoBuilder {
  constructor(city) {
    this.city = city;
    this.reserve = Math.max(1500, Math.round(START_MONEY * 0.12));
  }

  get g() { return this.city.grid; }
  canAfford(cost) { return this.city.money - this.reserve >= cost; }

  /** Run one pass; returns human-readable list of what was built. */
  run() {
    const actions = [];
    for (const step of [this.stepGenesis, this.stepPower, this.stepRoadAccess,
      this.stepDistricts, this.stepServices, this.stepLeisure]) {
      try { const r = step.call(this); if (r) { actions.push(r); this.city.recomputePower(); } } catch { /* keep going */ }
    }
    return actions;
  }

  /** Blank-map bootstrap: lay a main street through open land and zone
   *  homes on one side, shops + industry on the other. */
  stepGenesis() {
    const g = this.g, S = g.size;
    let any = false;
    for (let i = 0; i < g.n && !any; i++) if (g.struct[i] || g.zone[i]) any = true;
    if (any) return null;

    // find the longest clear horizontal runway near the vertical middle
    let best = null, bestLen = 0;
    for (let z = (S >> 2); z < S - (S >> 2); z++) {
      let run = 0, startX = -1;
      for (let x = 2; x < S - 2; x++) {
        const j = g.idx(x, z);
        if (g.terrain[j] === 0 && !g.rubble[j]) {
          if (startX < 0) startX = x;
          run++;
          if (run > bestLen) { bestLen = run; best = [startX, z]; }
        } else { run = 0; startX = -1; }
      }
    }
    if (!best || bestLen < 12) return null;

    const [x0, z] = best;
    const len = Math.min(bestLen, 16);
    if (!this.canAfford(len * BUILDINGS[STRUCT.ROAD].cost)) return null;
    for (let k = 0; k < len; k++) this.city.placeStruct(STRUCT.ROAD, x0 + k, z);

    const mid = x0 + ((len / 2) | 0);
    const band = (zx0, zx1, zz0, zz1, zid) => {
      const tiles = [];
      for (let zz = zz0; zz <= zz1; zz++) for (let xx = zx0; xx <= zx1; xx++) {
        if (!g.inB(xx, zz)) continue;
        const j = g.idx(xx, zz);
        if (this.freeForBuilding(j)) tiles.push([xx, zz]);
      }
      if (tiles.length >= 6) this.city.placeZone(zid, tiles);
    };
    const half = ((len / 2) | 0) - 1;
    band(x0, x0 + len - 1, z - 3, z - 1, ZONE.RES);
    band(mid - half, mid + half, z + 1, z + 2, ZONE.COM);
    band(mid + 2, x0 + len - 1, z + 1, z + 3, ZONE.IND);
    return `Founded the town: main street \u00d7${len} with homes, shops and industry`;
  }

  // ---------- helpers ----------

  freeForBuilding(i) {
    const g = this.g;
    return g.terrain[i] === 0 && !g.struct[i] && !g.rubble[i] && !g.burning[i] && !g.level[i];
  }

  findSpot(w, h, cx, cz, allowZone = true) {
    const g = this.g;
    for (let r = 1; r < 42; r++) {
      for (let z = cz - r; z <= cz + r; z++) {
        for (let x = cx - r; x <= cx + r; x++) {
          let okSpot = true;
          for (let dz = -1; dz <= h && okSpot; dz++) {
            for (let dx = -1; dx <= w; dx++) {
              if (!g.inB(x + dx, z + dz)) { okSpot = false; break; }
              const j = g.idx(x + dx, z + dz);
              const blocked = !this.freeForBuilding(j) || (allowZone ? false : !!g.zone[j]);
              // allow claiming EMPTY zoned plots (same rule as manual placement)
              const zonedOk = allowZone && g.zone[j] !== 0 && !g.level[j] && !g.struct[j] && !g.rubble[j] && !g.burning[j] && g.terrain[j] === 0;
              if (blocked && !zonedOk) { okSpot = false; break; }
            }
          }
          if (okSpot) return [x, z];
        }
      }
    }
    return null;
  }

  place(sid, w = 1, h = 1, nearX = 32, nearZ = 32) {
    const cost = BUILDINGS[sid].cost;
    if (!this.canAfford(cost)) return null;
    const p = this.findSpot(w, h, nearX, nearZ);
    if (!p) return null;
    const r = this.city.placeStruct(sid, p[0], p[1]);
    return r.ok ? p : null;
  }

  developedCentroid() {
    const g = this.g;
    let sx = 0, sz = 0, n = 0;
    for (let i = 0; i < g.n; i++) {
      if (g.level[i] > 0 || g.struct[i]) {
        sx += i % g.size; sz += (i / g.size) | 0; n++;
      }
    }
    if (!n) return null;
    return [(sx / n) | 0, (sz / n) | 0];
  }

  avgCoverage(map) {
    const g = this.g;
    let sum = 0, n = 0;
    for (let i = 0; i < g.n; i++) {
      if (g.level[i] > 0) { sum += map[i]; n++; }
    }
    return n ? sum / n : 1; // nothing developed → services not needed yet
  }

  // ---------- steps ----------

  stepPower() {
    const s = this.city.stats;
    // plan ahead: zoned land with zero capacity needs the first plant NOW
    let hasZones = false;
    for (let i = 0; i < this.g.n && !hasZones; i++) if (this.g.zone[i]) hasZones = true;
    const strain = s.brownouts > 0 || s.powerUse > s.powerCap * 0.65 ||
      (s.powerCap === 0 && hasZones);
    if (!strain) return null;
    const deficit = Math.max(600, s.powerUse * 0.8 - s.powerCap);
    let sid;
    if (deficit > 3000 && this.canAfford(BUILDINGS[STRUCT.COAL].cost)) sid = STRUCT.COAL;
    else if (deficit > 1200 && this.canAfford(BUILDINGS[STRUCT.SOLAR].cost)) sid = STRUCT.SOLAR;
    else sid = STRUCT.WIND;
    const c = this.developedCentroid() || [32, 32];
    const p = this.place(sid, BUILDINGS[sid].w, BUILDINGS[sid].h, c[0], c[1]);
    return p ? `${BUILDINGS[sid].name} for ${deficit | 0}W deficit` : null;
  }

  /** Zoned-empty plots with no road within 2 tiles never develop — connect them. */
  stepRoadAccess() {
    const g = this.g, S = g.size;
    let orphan = null;
    outer:
    for (let z = 2; z < S - 2; z++) {
      for (let x = 2; x < S - 2; x++) {
        const i = g.idx(x, z);
        if (g.zone[i] && !g.level[i] && !g.struct[i] && !g.rubble[i]) {
          if (!this.city.roadNear(x, z, 3)) { orphan = [x, z]; break outer; }
        }
      }
    }
    if (!orphan) return null;

    // BFS to nearest existing road through buildable land, then lay it
    const [ox, oz] = orphan;
    const prev = new Map();
    const q = [[ox, oz]];
    const seen = new Set([g.idx(ox, oz)]);
    let goal = null;
    while (q.length && !goal) {
      const [x, z] = q.shift();
      for (const [dx, dz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
        const nx = x + dx, nz = z + dz;
        if (!g.inB(nx, nz)) continue;
        const j = g.idx(nx, nz);
        if (seen.has(j)) continue;
        seen.add(j);
        if (g.struct[j] === STRUCT.ROAD) { goal = [nx, nz]; prev.set(j, g.idx(x, z)); break; }
        if (g.terrain[j] !== 0 || g.struct[j] || g.rubble[j] || g.level[j]) continue;
        prev.set(j, g.idx(x, z));
        q.push([nx, nz]);
      }
    }
    if (!goal) return null;

    // walk back from goal to orphan, laying road on every tile except the
    // final one adjacent to the orphan (that's the connection point)
    let laid = 0;
    let cur = g.idx(goal[0], goal[1]);
    const orphanIdx = g.idx(ox, oz);
    while (cur !== orphanIdx && laid < 14) {
      const x = cur % S, z = (cur / S) | 0;
      if (g.struct[cur] !== STRUCT.ROAD) {
        if (!this.canAfford(BUILDINGS[STRUCT.ROAD].cost)) break;
        this.city.placeStruct(STRUCT.ROAD, x, z);
        laid++;
      }
      if (!prev.has(cur)) break;
      cur = prev.get(cur);
    }
    return laid ? `Road spur ×${laid} to reach zoned land` : null;
  }

  /** Stamp a fresh district (road spur + flanking zone bands) for the
   *  demand type that most needs land. */
  stepDistricts() {
    const s = this.city.stats;
    const needs = [
      { d: s.resDemand, z: ZONE.RES, name: 'Homes' },
      { d: s.comDemand, z: ZONE.COM, name: 'Shops' },
      { d: s.indDemand, z: ZONE.IND, name: 'Industry' }
    ].sort((a, b) => b.d - a.d);
    const want = needs[0];
    if (want.d < 0.22) return null;

    // count empty zoned plots for that type; plenty left → no need
    const g = this.g;
    let empty = 0;
    for (let i = 0; i < g.n; i++) {
      if (g.zone[i] === want.z && !g.level[i] && !g.struct[i] &&
          this.city.roadNear(i % g.size, (i / g.size) | 0, 2)) empty++;
    }
    if (empty >= 14) return null;

    // seed: road tile with the most buildable space around its far end
    const seeds = [];
    for (let i = 0; i < g.n; i++) {
      if (g.struct[i] !== STRUCT.ROAD) continue;
      seeds.push(i);
    }
    if (!seeds.length) return null;
    let best = null, bestScore = -1;
    for (const si of seeds) {
      const sx = si % g.size, sz = (si / g.size) | 0;
      for (const [dx, dz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
        // measure open runway 6 tiles out in this direction
        let open = 0;
        for (let k = 1; k <= 6; k++) {
          const x = sx + dx * k, z = sz + dz * k;
          if (!g.inB(x, z)) break;
          const j = g.idx(x, z);
          if (g.terrain[j] !== 0 || g.struct[j] || g.level[j] || g.rubble[j]) break;
          open++;
        }
        if (open > bestScore) { bestScore = open; best = [sx, sz, dx, dz]; }
      }
    }
    if (!best || bestScore < 4) return null;
    const [sx, sz, dx, dz] = best;

    // budget guard for the whole district
    const estCost = (bestScore + 1) * BUILDINGS[STRUCT.ROAD].cost;
    if (!this.canAfford(estCost)) return null;

    // lay the spur
    let endX = sx, endZ = sz;
    for (let k = 1; k <= bestScore; k++) {
      endX = sx + dx * k; endZ = sz + dz * k;
      this.city.placeStruct(STRUCT.ROAD, endX, endZ);
    }
    // perpendicular axis for bands
    const px = dz !== 0 ? 1 : 0, pz = dx !== 0 ? 1 : 0;
    for (const side of [1, -1]) {
      const tiles = [];
      for (let along = -2; along <= 2; along++) {
        for (let depth = 1; depth <= 3; depth++) {
          const bx = endX + px * side * depth + dx * along;
          const bz = endZ + pz * side * depth + dz * along;
          if (!g.inB(bx, bz)) continue;
          const j = g.idx(bx, bz);
          if (this.freeForBuilding(j)) tiles.push([bx, bz]);
        }
      }
      if (tiles.length >= 6) this.city.placeZone(want.z, tiles);
    }
    return `${want.name} district: spur + two zone bands`;
  }

  stepServices() {
    const c = this.city;
    const plan = [
      { sid: STRUCT.POLICE, map: c.mapPolice, name: 'Police' },
      { sid: STRUCT.FIRE, map: c.mapFire, name: 'Fire Stn' },
      { sid: STRUCT.HOSPITAL, map: c.mapHealth, name: 'Hospital' },
      { sid: STRUCT.SCHOOL, map: c.mapEdu, name: 'School' }
    ];
    const worst = plan
      .map(p => ({ ...p, avg: this.avgCoverage(p.map) }))
      .sort((a, b) => a.avg - b.avg)[0];
    if (!worst || worst.avg > 0.34) return null;
    const ctr = this.developedCentroid();
    if (!ctr) return null;
    // services like being NEAR town but not in the middle of zones — search
    // outward from centroid; empty-zone claims are allowed
    const p = this.place(worst.sid, 1, 1, ctr[0] + 4, ctr[1] + 3);
    return p ? `${worst.name} (coverage was ${(worst.avg * 100) | 0}%)` : null;
  }

  stepLeisure() {
    if (this.avgCoverage(this.city.mapLeisure) > 0.30) return null;
    if (this.city.stats.pop < 60) return null;
    const ctr = this.developedCentroid();
    if (!ctr) return null;
    const pick = this.canAfford(BUILDINGS[STRUCT.PLAZA].cost) &&
      Math.random() < 0.5 ? STRUCT.PLAZA : STRUCT.PARK;
    const p = this.place(pick, 1, 1, ctr[0] - 3, ctr[1] + 4);
    return p ? BUILDINGS[pick].name : null;
  }
}
