import * as THREE from "three";
import type { PositionedNode } from "./graph-data";

// ─── Canvas-based node sprites ──────────────────────────────────────────────

const spriteCache = new Map<string, THREE.Sprite>();

/**
 * Creates a Three.js sprite for a graph node: colored circle with value label.
 */
export function createNodeSprite(node: PositionedNode): THREE.Sprite {
  const cacheKey = `${node.id}:${node.value}:${node.color}`;
  const cached = spriteCache.get(cacheKey);
  if (cached) return cached.clone();

  const canvas = document.createElement("canvas");
  const size = 512;
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext("2d")!;

  const cx = size / 2;
  const cy = size / 2;
  const radius = size / 2 - 16;

  // Outer glow ring
  const gradient = ctx.createRadialGradient(cx, cy, radius * 0.8, cx, cy, radius);
  gradient.addColorStop(0, node.color + "aa");
  gradient.addColorStop(1, node.color + "00");
  ctx.beginPath();
  ctx.arc(cx, cy, radius, 0, Math.PI * 2);
  ctx.fillStyle = gradient;
  ctx.fill();

  // Dark background circle for contrast
  ctx.beginPath();
  ctx.arc(cx, cy, radius * 0.78, 0, Math.PI * 2);
  ctx.fillStyle = "#0f172a";
  ctx.fill();

  // Colored border ring
  ctx.beginPath();
  ctx.arc(cx, cy, radius * 0.78, 0, Math.PI * 2);
  ctx.strokeStyle = node.color;
  ctx.lineWidth = 8;
  ctx.stroke();

  // Subtle inner fill
  ctx.beginPath();
  ctx.arc(cx, cy, radius * 0.74, 0, Math.PI * 2);
  ctx.fillStyle = node.color + "20";
  ctx.fill();

  // Label (top)
  ctx.fillStyle = "#ffffff";
  ctx.font = "bold 36px system-ui, sans-serif";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.shadowColor = "#000000";
  ctx.shadowBlur = 6;

  const label = node.label.length > 16 ? node.label.slice(0, 14) + "..." : node.label;
  ctx.fillText(label, cx, cy - 28);

  // Value (bottom) — brighter, larger
  ctx.fillStyle = node.color;
  ctx.font = "bold 44px system-ui, sans-serif";
  ctx.shadowBlur = 4;
  const valueStr = typeof node.value === "number" ? node.value.toFixed(1) : String(node.value);
  const displayValue = valueStr.length > 12 ? valueStr.slice(0, 10) + "..." : valueStr;
  ctx.fillText(displayValue, cx, cy + 24);

  // Unit (small, below value)
  if (node.unit) {
    ctx.fillStyle = "#94a3b8";
    ctx.font = "24px system-ui, sans-serif";
    ctx.shadowBlur = 0;
    ctx.fillText(node.unit, cx, cy + 60);
  }

  const texture = new THREE.CanvasTexture(canvas);
  const material = new THREE.SpriteMaterial({
    map: texture,
    transparent: true,
    depthWrite: false,
  });
  const sprite = new THREE.Sprite(material);
  sprite.scale.set(50, 50, 1);

  spriteCache.set(cacheKey, sprite);
  return sprite;
}

/**
 * Creates a dimmed version of a node sprite (for non-highlighted nodes).
 */
export function createDimmedNodeSprite(node: PositionedNode): THREE.Sprite {
  const sprite = createNodeSprite({ ...node, color: "#4b5563" });
  sprite.material.opacity = 0.3;
  return sprite;
}

// ─── Link particle rendering ───────────────────────────────────────────────

/**
 * Returns a color for a link based on the source node's domain color.
 */
export function getLinkColor(
  link: { source: string | { color?: string }; weight: number },
  opacity = 0.4,
): string {
  const sourceColor = typeof link.source === "object" && link.source.color
    ? link.source.color
    : "#6b7280";
  // Convert hex to rgba
  const r = parseInt(sourceColor.slice(1, 3), 16);
  const g = parseInt(sourceColor.slice(3, 5), 16);
  const b = parseInt(sourceColor.slice(5, 7), 16);
  return `rgba(${r},${g},${b},${opacity})`;
}
