import { VoxelGrid } from "./src/grid/VoxelGrid.js";
import { CrystalSpiresGenerator } from "./src/generation/CrystalSpiresGenerator.js";

// 1. Get Seed from Args
const args = process.argv.slice(2);
const seed = args.length > 0 ? parseInt(args[0]) : 727852;
console.log(`Debug Run: Seed ${seed}`);

// Mock OffscreenCanvas for Node.js environment
global.OffscreenCanvas = class {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  getContext() {
    return {
      fillStyle: "",
      fillRect: () => {},
      beginPath: () => {},
      moveTo: () => {},
      lineTo: () => {},
      stroke: () => {},
      fill: () => {},
      arc: () => {},
      save: () => {},
      restore: () => {},
      translate: () => {},
      rotate: () => {},
      scale: () => {},
      createLinearGradient: () => ({ addColorStop: () => {} }),
      createRadialGradient: () => ({ addColorStop: () => {} }),
      createImageData: (w, h) => ({
        width: w,
        height: h,
        data: new Uint8ClampedArray(w * h * 4),
      }),
      putImageData: () => {},
    };
  }
};

const grid = new VoxelGrid(20, 20, 20);
const generator = new CrystalSpiresGenerator(grid, seed);

generator.generate(4, 3);

// 2. Validate Bridges
const bridges = generator.generatedAssets.bridges || [];
const spires = generator.generatedAssets.spires || [];

console.log(`\n=== Bridge Validity Check ===`);
console.log(`Total Spires: ${spires.length}`);
console.log(`Total Bridges: ${bridges.length}`);

// Helper: Collision Check
function checkCylinderCollision(x1, z1, x2, z2, cx, cz, r) {
  const dx = x2 - x1;
  const dz = z2 - z1;
  const fx = x1 - cx;
  const fz = z1 - cz;
  const a = dx * dx + dz * dz;
  const b = 2 * (fx * dx + fz * dz);
  const c = fx * fx + fz * fz - r * r;
  let discrim = b * b - 4 * a * c;
  if (discrim < 0) return false;
  discrim = Math.sqrt(discrim);
  const t1 = (-b - discrim) / (2 * a);
  const t2 = (-b + discrim) / (2 * a);
  if ((t1 > 0.05 && t1 < 0.95) || (t2 > 0.05 && t2 < 0.95)) return true;
  return false;
}

// Helper: Check if a position is valid to stand on
function isValidStance(grid, x, y, z) {
  if (
    x < 0 ||
    x >= grid.size.x ||
    z < 0 ||
    z >= grid.size.z ||
    y < 0 ||
    y >= grid.size.y
  )
    return false;
  const ground = grid.getCell(x, y - 1, z);
  const feet = grid.getCell(x, y, z);
  const head = grid.getCell(x, y + 1, z);
  if (ground === 0 || feet !== 0 || head !== 0) {
    // console.log(`Stance Fail @ ${x},${y},${z}: G=${ground} F=${feet} H=${head}`);
    return false;
  }
  return true;
}

// Helper: BFS Pathfinding
// Helper: Game-Accurate Walkability Check (Matches MovementSystem.js)
function checkWalkability(grid, start, end, spireCenter = null) {
  const q = [];
  const visited = new Set();
  const cameFrom = new Map(); // Track path for reconstruction

  // Find valid start Y (Unit stands ON TOP of the block)
  // Generator `start` is the Bridge Block (Floor).
  // So Unit Y = start.y + 1.
  const sx = Math.round(start.x);
  const sz = Math.round(start.z);

  // Find Walkable Y logic from MovementSystem
  // Checks: Cell(y) != 0 is FAIL. Cell(y-1) == 0 is FAIL. Cell(y+1) != 0 is FAIL.
  const findWalkableY = (x, z, refY) => {
    const levels = [refY, refY + 1, refY - 1, refY - 2];
    for (const y of levels) {
      if (
        grid.getCell(x, y, z) === 0 && // Feet Clear
        grid.getCell(x, y - 1, z) !== 0 && // Floor Solid
        grid.getCell(x, y + 1, z) === 0
      ) {
        // Head Clear
        return y;
      }
    }
    return null;
  };

  const sy = findWalkableY(sx, sz, Math.round(start.y) + 1);
  if (sy === null) {
    return {
      success: false,
      reason: `Invalid Start Stance near ${sx},${start.y},${sz}`,
    };
  }

  const ex = Math.round(end.x);
  const ez = Math.round(end.z);
  // Target Y is flexible (logic finds it)

  q.push({ x: sx, y: sy, z: sz, dist: 0 });
  const startKey = `${sx},${sy},${sz}`;
  visited.add(startKey);
  cameFrom.set(startKey, null);

  while (q.length > 0) {
    const curr = q.shift();

    // Target Reached? (Approximate X/Z matches)
    // Note: We check if we are 'standing' on or near the target platform rim
    if (Math.abs(curr.x - ex) <= 2 && Math.abs(curr.z - ez) <= 2) {
      // Also check Y proximity?
      if (Math.abs(curr.y - (end.y + 1)) <= 3) {
        // Perform Outward Check if needed
        if (spireCenter) {
          // Reconstruct Path
          let path = [];
          let k = `${curr.x},${curr.y},${curr.z}`;
          while (k) {
            const [px, py, pz] = k.split(",").map(Number);
            path.push({ x: px, y: py, z: pz });
            k = cameFrom.get(k);
          }
          path.reverse();

          // Check first 3 steps
          if (path.length > 1) {
            const p0 = path[0];
            // Check a bit further steps to be sure
            const checkIdx = Math.min(path.length - 1, 3);
            const pCheck = path[checkIdx];

            const d0 = Math.sqrt(
              Math.pow(p0.x - spireCenter.x, 2) +
                Math.pow(p0.z - spireCenter.z, 2)
            );
            const dN = Math.sqrt(
              Math.pow(pCheck.x - spireCenter.x, 2) +
                Math.pow(pCheck.z - spireCenter.z, 2)
            );

            if (dN < d0) {
              // Check if we are walking on a BRIDGE (ID 20) or PLATFORM (ID != 20)
              // If on Platform, Inward is valid (walking across).
              // If on Bridge, Inward is invalid (curling back).
              // `pCheck` is at feet level? No, pCheck has .y stance.
              // Grid cell at y-1 is floor.
              const floorID = grid.getCell(
                Math.round(pCheck.x),
                Math.round(pCheck.y - 1),
                Math.round(pCheck.z)
              );

              if (floorID === 20) {
                console.log(
                  `[DEBUG] Inward Fail (Bridge Voxel): Center(${spireCenter.x}, ${spireCenter.z})`
                );
                console.log(
                  `[DEBUG] Path[0]: (${p0.x}, ${p0.y}, ${p0.z}) D=${d0.toFixed(
                    2
                  )}`
                );
                console.log(
                  `[DEBUG] Path[${checkIdx}]: (${pCheck.x}, ${pCheck.y}, ${
                    pCheck.z
                  }) D=${dN.toFixed(2)} ID=${floorID}`
                );
                return {
                  success: false,
                  reason:
                    "Path goes INWARD towards spire center (Bridge Recurve)",
                };
              } else {
                // console.log(`[DEBUG] Inward Move Allowed on Platform (ID ${floorID})`);
              }
            }
          }
        }
        return { success: true };
      }
    }

    if (curr.dist > 300) continue;

    // Spec CoA Implementation
    // 1. Neighbors (Horizontal Only)
    const dirs = [
      { x: 1, z: 0 },
      { x: -1, z: 0 },
      { x: 0, z: 1 },
      { x: 0, z: -1 },
    ];

    for (const d of dirs) {
      const nx = curr.x + d.x;
      const nz = curr.z + d.z;

      if (!grid.isValidBounds(nx, 0, nz)) continue;

      // CoA 1: Gradient <= 1
      // Check Step Up (y+1), Level (y), Step Down (y-1)
      const candidates = [curr.y, curr.y + 1, curr.y - 1];

      for (const ny of candidates) {
        if (!grid.isValidBounds(nx, ny, nz)) continue;

        // CoA 3 & 4: Headroom Validation
        // Floor(ny-1) must be solid.
        // Feet(ny) can be Air, Bridge(20), Teleporter(22).
        // Head1(ny+1) MUST be Air.
        // Head2(ny+2) MUST be Air.

        const floor = grid.getCell(nx, ny - 1, nz);
        const feet = grid.getCell(nx, ny, nz);
        const h1 = grid.getCell(nx, ny + 1, nz);
        const h2 = grid.getCell(nx, ny + 2, nz);

        const isFloorSolid = floor !== 0;
        const isFeetPassable = feet === 0 || feet === 20 || feet === 22;
        const isHeadClear = h1 === 0 && h2 === 0;

        if (isFloorSolid && isFeetPassable && isHeadClear) {
          const key = `${nx},${ny},${nz}`;
          if (!visited.has(key)) {
            visited.add(key);
            cameFrom.set(key, `${curr.x},${curr.y},${curr.z}`);
            q.push({ x: nx, y: ny, z: nz, dist: curr.dist + 1 });
          }
        }
      }
    }
  }

  return { success: false, reason: "No Path Found (Gap or Blockage)" };
}

let validBridges = 0;
bridges.forEach((b, idx) => {
  let isValid = true;
  let issues = [];

  // Check 1: Pillar Collision
  for (let i = 0; i < spires.length; i++) {
    if (i === b.fromSpire || i === b.toSpire) continue;
    const s = spires[i];
    if (
      checkCylinderCollision(
        b.start.x,
        b.start.z,
        b.end.x,
        b.end.z,
        s.x,
        s.z,
        s.radius + 1.0
      )
    ) {
      isValid = false;
      issues.push(`Clips Spire ${i}`);
    }
  }

  // Check 2: Slope
  const yDiff = Math.abs(b.end.y - b.start.y);
  const rimDist = b.dist;
  const reqYdMax = Math.max(rimDist, 3) * 3.0;
  if (yDiff > 12) {
    isValid = false;
    issues.push(`Too steep (Y=${yDiff} > 12)`);
  }
  if (yDiff > reqYdMax) {
    isValid = false;
    issues.push(`Slope limit exceeded`);
  }

  // Bridge Validity Logic (CoA Compliance)
  // 1. Walkability (Forward)
  const walkA = checkWalkability(grid, b.start, b.end, { x: 0, y: 0, z: 0 }); // Centroid irrelevant now

  // 2. Walkability (Reverse)
  const walkB = checkWalkability(grid, b.end, b.start, { x: 0, y: 0, z: 0 });

  if (!walkA.success) {
    isValid = false;
    issues.push(`Forward Unwalkable: ${walkA.reason}`);
  }
  if (isValid && !walkB.success) {
    isValid = false;
    issues.push(`Reverse Unwalkable: ${walkB.reason}`);
  }

  // Note: We removed strict "Inward" and "Tortuosity" checks as Bezier curves are organic.
  // The primary constraints are now Gradient (in checkWalkability) and Headroom.

  if (isValid) {
    console.log(
      `Bridge #${idx} [Spire ${b.fromSpire} -> ${b.toSpire}]: VALID (OK)`
    );
    validBridges++;
  } else {
    console.log(
      `Bridge #${idx} [Spire ${b.fromSpire} -> ${
        b.toSpire
      }]: INVALID (${issues.join(", ")})`
    );
  }
});

console.log(`\n=== Orphan Platform Check ===`);
let orphans = 0;
let totalPlatforms = 0;

spires.forEach((s, sIdx) => {
  s.platforms.forEach((p, pIdx) => {
    totalPlatforms++;
    // Check if this platform has any connections recorded in its object
    // Note: connections array stores points, so strictly length > 0
    if (!p.connections || p.connections.length === 0) {
      orphans++;
      console.log(
        `Spire ${sIdx} Platform ${pIdx} (Y=${p.y}) is ORPHANED (0 connections)`
      );
    }
  });
});

console.log(`Total Platforms: ${totalPlatforms}`);
console.log(`Orphan Platforms: ${orphans}`);

console.log(`\n=== Graph Connectivity Check ===`);
// 1. Gather all platforms and assign IDs
let allPlats = [];
spires.forEach((s, sIdx) => {
  s.platforms.forEach((p, pIdx) => {
    p.id = `${sIdx}:${pIdx}`;
    allPlats.push(p);
  });
});
allPlats.sort((a, b) => a.y - b.y);

// 2. Build Adjacency Graph from Bridges
// Bridge stores: { fromSpire, toSpire, start, end }
// We need to map start/end points back to platforms.
// Helper: Find platform containing point
function findPlat(pt, sIdx) {
  const s = spires[sIdx];
  // Find plat with closest Y and matching X/Z within radius
  let best = null;
  let minD = Infinity;
  for (const p of s.platforms) {
    const dy = Math.abs(p.y - pt.y);
    if (dy < 2.0) return p; // Direct height match usually enough
  }
  return null;
}

const adj = new Map(); // ID -> Set<ID>
allPlats.forEach((p) => adj.set(p.id, new Set()));

bridges.forEach((b) => {
  // Note: b.fromSpire/b.toSpire are indices
  // b.start is on fromSpire, b.end is on toSpire
  const pA = findPlat(b.start, b.fromSpire);
  const pB = findPlat(b.end, b.toSpire);

  if (pA && pB) {
    adj.get(pA.id).add(pB.id);
    adj.get(pB.id).add(pA.id);
    // console.log(`[Graph] Linked ${pA.id} <--> ${pB.id} via Bridge ${bridges.indexOf(b)}`);
  } else {
    console.log(
      `[Graph] Failed to map bridge ${bridges.indexOf(b)} to platforms!`
    );
    console.log(
      `  Bridge: Spire ${b.fromSpire} -> ${b.toSpire}, Y: ${b.start.y.toFixed(
        1
      )} -> ${b.end.y.toFixed(1)}`
    );
    console.log(`  pA: ${pA ? pA.id : "NULL"}, pB: ${pB ? pB.id : "NULL"}`);
  }
});

// 3. BFS from Lowest Platform
const startNode = allPlats[0];
const targetNode = allPlats[allPlats.length - 1]; // Highest
const qGraph = [startNode.id];
const visitedGraph = new Set([startNode.id]);

while (qGraph.length > 0) {
  const curr = qGraph.shift();
  const neighbors = adj.get(curr);
  if (neighbors) {
    for (const n of neighbors) {
      if (!visitedGraph.has(n)) {
        visitedGraph.add(n);
        qGraph.push(n);
      }
    }
  }
}

// 4. Report
const fullyConnected = visitedGraph.size === allPlats.length;
const topReachable = visitedGraph.has(targetNode.id);

console.log(
  `Nodes Reachable from Bottom (Y=${startNode.y}): ${visitedGraph.size} / ${allPlats.length}`
);
console.log(
  `Top Platform (Y=${targetNode.y}) Reachable: ${topReachable ? "YES" : "NO"}`
);
console.log(`Full Graph Interconnected: ${fullyConnected ? "YES" : "NO"}`);

// CoA 5: Global Reachability via Grid Flood Fill (True Walkability)
// Graph connectivity assumes bridges work. Flood Fill PROVES it.
console.log("\n=== Global Flood Fill (Spawn -> All) ===");

// DEBUG: Check specific coordinate reported by Generator logic
const checkVal = grid.getCell(12, 11, 5);
console.log(`DEBUG CHECK: Cell(12,11,5) = ${checkVal}`);

// 0. Find all Teleporters (ID 22)
const teleporters = [];
for (let x = 0; x < grid.width; x++) {
  for (let z = 0; z < grid.depth; z++) {
    for (let y = 0; y < grid.height; y++) {
      if (grid.getCell(x, y, z) === 22) {
        teleporters.push({ x, y, z });
      }
    }
  }
}
console.log(`Found ${teleporters.length} Teleporter Nodes.`);

const spawnPlat = allPlats[0];
const reachableSet = new Set();
// Start slightly above spawn platform to ensure standing
const floodQ = [
  {
    x: Math.round(spawnPlat.x),
    y: Math.round(spawnPlat.y + 1),
    z: Math.round(spawnPlat.z),
  },
];
const visitedFlood = new Set([`${floodQ[0].x},${floodQ[0].y},${floodQ[0].z}`]);

// Optimization: Limit flood fill iterations
let iterations = 0;
const MAX_ITER = 50000;

while (floodQ.length > 0 && iterations < MAX_ITER) {
  iterations++;
  const curr = floodQ.shift();

  // Check Teleport Jump
  const floorID = grid.getCell(curr.x, curr.y - 1, curr.z);
  const feetID = grid.getCell(curr.x, curr.y, curr.z);

  if (floorID === 22 || feetID === 22) {
    for (const t of teleporters) {
      const destKey = `${t.x},${t.y + 1},${t.z}`;
      if (!visitedFlood.has(destKey)) {
        visitedFlood.add(destKey);
        floodQ.push({ x: t.x, y: t.y + 1, z: t.z });
      }
    }
  }

  // Neighbors (Same logic as checkWalkability)
  const dirs = [
    { x: 1, z: 0 },
    { x: -1, z: 0 },
    { x: 0, z: 1 },
    { x: 0, z: -1 },
  ];
  for (const d of dirs) {
    const nx = curr.x + d.x;
    const nz = curr.z + d.z;
    if (!grid.isValidBounds(nx, 0, nz)) continue;

    const candidates = [curr.y, curr.y + 1, curr.y - 1];
    for (const ny of candidates) {
      if (!grid.isValidBounds(nx, ny, nz)) continue;

      const floor = grid.getCell(nx, ny - 1, nz);
      const feet = grid.getCell(nx, ny, nz);
      const h1 = grid.getCell(nx, ny + 1, nz);
      const h2 = grid.getCell(nx, ny + 2, nz);

      const isFloorSolid = floor !== 0;
      // Allow ID 22 (Teleporter Node) as passable
      const isPassable = feet === 0 || feet === 20 || feet === 22;

      if (isFloorSolid && isPassable && h1 === 0 && h2 === 0) {
        const key = `${nx},${ny},${nz}`;
        if (!visitedFlood.has(key)) {
          visitedFlood.add(key);
          floodQ.push({ x: nx, y: ny, z: nz });
        }
      }
    }
  }
}

// Check Coverage
let floodReachableCount = 0;
allPlats.forEach((p, idx) => {
  let hit = false;
  const searchR = p.radius - 1;
  for (let x = Math.round(p.x - searchR); x <= Math.round(p.x + searchR); x++) {
    for (
      let z = Math.round(p.z - searchR);
      z <= Math.round(p.z + searchR);
      z++
    ) {
      // Check surface (y) or just above (y+1)
      if (
        visitedFlood.has(`${x},${Math.round(p.y + 1)},${z}`) ||
        visitedFlood.has(`${x},${Math.round(p.y)},${z}`)
      ) {
        hit = true;
        break;
      }
    }
    if (hit) break;
  }
  if (hit) floodReachableCount++;
  else
    console.log(`[FloodFail] Platform ${p.globalId} unreachable from Spawn.`);
});

console.log(
  `Flood Fill Reachable Platforms: ${floodReachableCount} / ${allPlats.length}`
);
console.log(
  `Global Reachability: ${
    floodReachableCount === allPlats.length ? "PASS" : "FAIL"
  }`
);

console.log(`\nGeneration Complete.`);
