import type { NetworkNode } from "@/api/client";
import { statusRGBA, type RGBA, type StatusColors } from "./graphColors";

export const SPACE_SIZE = 4096;
const CENTER = SPACE_SIZE / 2;

export interface BuiltGraph {
  entries: NetworkNode[];
  positions: Float32Array;
  colors: Float32Array;
  sizes: Float32Array;
  clusterIndices: (number | undefined)[];
  clusterPositions: (number | undefined)[];
  clusterStrength: Float32Array;
  clusterNames: string[];
  hashToIndex: Map<string, number>;
}

function findParentIface(
  entry: NetworkNode,
  interfaces: NetworkNode[],
  peerIndex: number,
): string | undefined {
  if (entry.interface) {
    const iface = interfaces.find(i => i.name === entry.interface);
    if (iface) return iface.hash;
  }
  if (interfaces.length > 0) return interfaces[peerIndex % interfaces.length].hash;
  return undefined;
}

export function buildGraphArrays(
  rawNodes: NetworkNode[],
  prevHashToIndex: Map<string, number>,
  prevPositions: number[],
  theme: StatusColors,
): BuiltGraph {
  const interfaces = rawNodes.filter(e => e.type === "interface").sort((a, b) => a.name.localeCompare(b.name));
  const selfNode = rawNodes.find(e => e.is_self && e.type !== "interface");
  const peers = rawNodes.filter(e => !e.is_self && e.type !== "interface");

  const entries = selfNode ? [selfNode, ...peers] : peers;
  const selfIndex = selfNode ? 0 : -1;
  const n = entries.length;
  const nClusters = interfaces.length;
  const hashToIndex = new Map<string, number>();
  const positions = new Float32Array(n * 2);
  const colors = new Float32Array(n * 4);
  const sizes = new Float32Array(n);
  const clusterIndices: (number | undefined)[] = [];
  const clusterStrength = new Float32Array(n);

  const clusterMap = new Map<string, number>();
  interfaces.forEach((iface, i) => clusterMap.set(iface.name, i));
  const selfClusterIndex = interfaces.length;

  const clusterNames = [...interfaces.map(i => i.name), ...(selfNode ? [selfNode.name] : [])];
  const clusterPositions: (number | undefined)[] = [];

  entries.forEach((entry, i) => {
    hashToIndex.set(entry.hash, i);

    // Preserve existing position, or jitter near center for new points
    const prevIdx = prevHashToIndex.get(entry.hash);
    if (prevIdx !== undefined && prevPositions.length >= (prevIdx + 1) * 2) {
      positions[i * 2] = prevPositions[prevIdx * 2]!;
      positions[i * 2 + 1] = prevPositions[prevIdx * 2 + 1]!;
    } else {
      // Tighter initial spread for small graphs so they don't scatter
      const spread = n <= 20 ? 0.1 : n <= 100 ? 0.25 : 0.5;
      positions[i * 2] = CENTER + (Math.random() - 0.5) * SPACE_SIZE * spread;
      positions[i * 2 + 1] = CENTER + (Math.random() - 0.5) * SPACE_SIZE * spread;
    }

    // Self node = bright white, others by status
    const rgba: RGBA = i === selfIndex
      ? [1, 1, 1, 1]
      : statusRGBA(entry, theme);
    colors[i * 4 + 0] = rgba[0];
    colors[i * 4 + 1] = rgba[1];
    colors[i * 4 + 2] = rgba[2];
    colors[i * 4 + 3] = rgba[3];

    // Scale node size inversely with count: bigger dots when fewer nodes
    sizes[i] = n <= 10 ? 8 : n <= 50 ? 5 : n <= 200 ? 4 : 3;

    // Cluster assignment
    if (i === selfIndex) {
      clusterIndices.push(selfClusterIndex);
    } else {
      const pi = peers.indexOf(entry);
      const parentHash = findParentIface(entry, interfaces, pi);
      const parentName = parentHash ? interfaces.find(f => f.hash === parentHash)?.name : undefined;
      clusterIndices.push(parentName !== undefined ? clusterMap.get(parentName) : undefined);
    }

    clusterStrength[i] = nClusters > 1 ? (nClusters - (i % nClusters)) / nClusters : 1;
  });

  return { entries, positions, colors, sizes, clusterIndices, clusterPositions, clusterStrength, clusterNames, hashToIndex };
}
