import type { TilePlacement } from "../../app/lib/types.ts";

/** Place annotations after all physical geometry is final. Moving or changing
 * a label never moves an entity, changes alignment or changes the snap period. */
export function placeFlowLabels(placements: TilePlacement[]) {
  const occupied = new Set<string>();
  const reserve = (p: TilePlacement) => {
    for (let y = Math.floor(p.y); y < p.y + p.height; y++) {
      for (let x = Math.floor(p.x); x < p.x + p.width; x++) occupied.add(`${x},${y}`);
    }
  };
  const free = (p: TilePlacement, x: number, y: number) => {
    for (let dy = 0; dy < p.height; dy++) for (let dx = 0; dx < p.width; dx++) {
      if (occupied.has(`${x + dx},${y + dy}`)) return false;
    }
    return true;
  };
  placements.filter((p) => p.kind !== "link").forEach(reserve);
  for (const label of placements.filter((p) => p.kind === "link")) {
    const anchor = label.labelAnchor ?? { x: label.x, y: label.y };
    label.labelAnchor = { ...anchor };
    search: for (let distance = 0; ; distance++) {
      // Prefer the same lane, then the nearest free row. All candidates stay
      // on integer tiles, including labels that move outside a power frame.
      for (let dy = 0; dy <= distance; dy++) {
        const dx = distance - dy;
        for (const sy of dy ? [-1, 1] : [0]) for (const sx of dx ? [-1, 1] : [0]) {
          const x = anchor.x + sx * dx, y = anchor.y + sy * dy;
          if (!free(label, x, y)) continue;
          label.x = x; label.y = y;
          reserve(label);
          break search;
        }
      }
    }
  }
}
