const header = document.querySelector("[data-header]");
const toggle = document.querySelector(".nav-toggle");
const navLinks = document.querySelectorAll(".site-nav a");
const form = document.querySelector(".contact-form");
const formNote = document.querySelector("[data-form-note]");
const canvas = document.querySelector("[data-network]");
const ctx = canvas?.getContext("2d");
const globeCanvas = document.querySelector("[data-globe]");
const globeCtx = globeCanvas?.getContext("2d");
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");

let width = 0;
let height = 0;
let ambientNodes = [];
let missionNodes = [];
let dataLinks = [];
let packets = [];
let tick = 0;
let globeTexture = null;
let globeRotation = 0;
let globeReady = false;
let networkAnimationId = null;
let globeAnimationId = null;

const nodeTemplate = [
  { label: "C2", x: 0.5, y: 0.44, r: 7, primary: true },
  { label: "ISR", x: 0.22, y: 0.28, r: 4.8 },
  { label: "M&S", x: 0.78, y: 0.26, r: 4.8 },
  { label: "OQE", x: 0.73, y: 0.68, r: 4.6 },
  { label: "OPS", x: 0.3, y: 0.7, r: 4.8 },
  { label: "AUT", x: 0.12, y: 0.55, r: 4.2 },
  { label: "SYN", x: 0.88, y: 0.52, r: 4.2 },
];

const linkTemplate = [
  [0, 1],
  [0, 2],
  [0, 3],
  [0, 4],
  [1, 5],
  [2, 6],
  [4, 5],
  [3, 6],
  [1, 4],
  [2, 3],
];

function setHeaderState() {
  header?.classList.toggle("is-scrolled", window.scrollY > 24);
}

function closeNav() {
  document.body.classList.remove("nav-open");
  header?.classList.remove("is-open");
  toggle?.setAttribute("aria-expanded", "false");
}

function resizeCanvas() {
  if (!canvas || !ctx) return;

  const rect = canvas.getBoundingClientRect();
  const scale = window.devicePixelRatio || 1;
  width = rect.width;
  height = rect.height;
  canvas.width = Math.floor(width * scale);
  canvas.height = Math.floor(height * scale);
  ctx.setTransform(scale, 0, 0, scale, 0, 0);

  const count = Math.max(42, Math.floor(width / 28));
  ambientNodes = Array.from({ length: count }, () => ({
    x: Math.random() * width,
    y: Math.random() * height,
    vx: (Math.random() - 0.5) * 0.18,
    vy: (Math.random() - 0.5) * 0.18,
    r: 0.8 + Math.random() * 1.3,
  }));

  missionNodes = nodeTemplate.map((node) => ({
    ...node,
    px: node.x * width,
    py: node.y * height,
  }));

  dataLinks = linkTemplate.map(([from, to], index) => ({
    from,
    to,
    phase: index * 0.13,
  }));

  packets = dataLinks.flatMap((link, index) => [
    { ...link, progress: (index * 0.17) % 1, speed: 0.0022 + (index % 3) * 0.00035 },
    { ...link, progress: (index * 0.17 + 0.48) % 1, speed: 0.0017 + (index % 4) * 0.00028 },
  ]);
}

function scheduleNetworkFrame() {
  if (networkAnimationId || reducedMotion.matches || document.hidden) return;
  networkAnimationId = requestAnimationFrame(drawNetwork);
}

function scheduleGlobeFrame() {
  if (globeAnimationId || reducedMotion.matches || document.hidden) return;
  globeAnimationId = requestAnimationFrame(drawGlobe);
}

function drawGridPulse() {
  const centerX = width * 0.5;
  const centerY = height * 0.45;
  const pulse = (Math.sin(tick * 0.018) + 1) / 2;

  ctx.save();
  ctx.strokeStyle = `rgba(52, 243, 226, ${0.07 + pulse * 0.05})`;
  ctx.lineWidth = 1;
  for (let i = 0; i < 4; i += 1) {
    ctx.beginPath();
    ctx.ellipse(centerX, centerY, 150 + i * 110 + pulse * 28, 62 + i * 42, -0.18, 0, Math.PI * 2);
    ctx.stroke();
  }
  ctx.restore();
}

function drawAmbientMesh() {
  for (const point of ambientNodes) {
    point.x += point.vx;
    point.y += point.vy;

    if (point.x < 0 || point.x > width) point.vx *= -1;
    if (point.y < 0 || point.y > height) point.vy *= -1;
  }

  for (let i = 0; i < ambientNodes.length; i += 1) {
    for (let j = i + 1; j < ambientNodes.length; j += 1) {
      const a = ambientNodes[i];
      const b = ambientNodes[j];
      const distance = Math.hypot(a.x - b.x, a.y - b.y);

      if (distance < 135) {
        const alpha = (1 - distance / 135) * 0.12;
        ctx.strokeStyle = `rgba(135, 240, 232, ${alpha})`;
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(b.x, b.y);
        ctx.stroke();
      }
    }
  }

  for (const point of ambientNodes) {
    ctx.fillStyle = "rgba(135, 240, 232, 0.36)";
    ctx.beginPath();
    ctx.arc(point.x, point.y, point.r, 0, Math.PI * 2);
    ctx.fill();
  }
}

function drawMissionLinks() {
  for (const link of dataLinks) {
    const a = missionNodes[link.from];
    const b = missionNodes[link.to];
    const gradient = ctx.createLinearGradient(a.px, a.py, b.px, b.py);
    gradient.addColorStop(0, "rgba(52, 243, 226, 0.04)");
    gradient.addColorStop(0.5, "rgba(52, 243, 226, 0.28)");
    gradient.addColorStop(1, "rgba(120, 145, 166, 0.08)");

    ctx.strokeStyle = gradient;
    ctx.lineWidth = a.primary || b.primary ? 1.6 : 1;
    ctx.beginPath();
    ctx.moveTo(a.px, a.py);
    ctx.lineTo(b.px, b.py);
    ctx.stroke();
  }
}

function drawPackets() {
  for (const packet of packets) {
    const from = missionNodes[packet.from];
    const to = missionNodes[packet.to];
    packet.progress = (packet.progress + packet.speed) % 1;

    const ease = packet.progress < 0.5
      ? 2 * packet.progress * packet.progress
      : 1 - Math.pow(-2 * packet.progress + 2, 2) / 2;
    const x = from.px + (to.px - from.px) * ease;
    const y = from.py + (to.py - from.py) * ease;
    const angle = Math.atan2(to.py - from.py, to.px - from.px);

    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(angle);
    ctx.fillStyle = "rgba(52, 243, 226, 0.92)";
    ctx.shadowColor = "rgba(52, 243, 226, 0.75)";
    ctx.shadowBlur = 14;
    ctx.beginPath();
    ctx.moveTo(8, 0);
    ctx.lineTo(-5, -3);
    ctx.lineTo(-2, 0);
    ctx.lineTo(-5, 3);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }
}

function drawMissionNodes() {
  for (const node of missionNodes) {
    const pulse = (Math.sin(tick * 0.035 + node.px * 0.01) + 1) / 2;
    const radius = node.primary ? 32 + pulse * 8 : 18 + pulse * 5;

    ctx.strokeStyle = `rgba(52, 243, 226, ${node.primary ? 0.34 : 0.2})`;
    ctx.lineWidth = node.primary ? 1.4 : 1;
    ctx.beginPath();
    ctx.arc(node.px, node.py, radius, 0, Math.PI * 2);
    ctx.stroke();

    ctx.fillStyle = node.primary ? "rgba(52, 243, 226, 0.98)" : "rgba(135, 240, 232, 0.82)";
    ctx.shadowColor = "rgba(52, 243, 226, 0.65)";
    ctx.shadowBlur = node.primary ? 22 : 12;
    ctx.beginPath();
    ctx.arc(node.px, node.py, node.r, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;

    if (width > 760) {
      ctx.font = "700 11px Inter, sans-serif";
      ctx.fillStyle = "rgba(243, 251, 255, 0.48)";
      ctx.fillText(node.label, node.px + node.r + 8, node.py - node.r - 6);
    }
  }
}

function drawNetwork() {
  networkAnimationId = null;
  if (!ctx || !canvas) return;

  tick += 1;
  ctx.clearRect(0, 0, width, height);
  ctx.fillStyle = "rgba(5, 8, 13, 0.38)";
  ctx.fillRect(0, 0, width, height);

  drawGridPulse();
  drawAmbientMesh();
  drawMissionLinks();
  drawPackets();
  drawMissionNodes();

  scheduleNetworkFrame();
}

function prepareGlobeTexture() {
  if (!globeCanvas || !globeCtx) return;

  const image = new Image();
  image.src = "assets/world-map-real.svg";
  image.onload = () => {
    const textureCanvas = document.createElement("canvas");
    const textureCtx = textureCanvas.getContext("2d", { willReadFrequently: true });
    textureCanvas.width = 720;
    textureCanvas.height = 360;
    textureCtx.fillStyle = "#000";
    textureCtx.fillRect(0, 0, textureCanvas.width, textureCanvas.height);
    textureCtx.drawImage(image, 0, 0, textureCanvas.width, textureCanvas.height);
    globeTexture = textureCtx.getImageData(0, 0, textureCanvas.width, textureCanvas.height);
    globeReady = true;
    drawGlobe();
  };
}

function sampleLand(texture, lon, lat) {
  const x = Math.floor(((lon + Math.PI) / (Math.PI * 2)) * (texture.width - 1));
  const y = Math.floor(((Math.PI / 2 - lat) / Math.PI) * (texture.height - 1));
  const index = (y * texture.width + x) * 4;
  const r = texture.data[index];
  const g = texture.data[index + 1];
  const b = texture.data[index + 2];
  const a = texture.data[index + 3];
  return a > 8 && r + g + b > 180;
}

function drawGlobeGrid(cx, cy, radius) {
  globeCtx.strokeStyle = "rgba(52, 243, 226, 0.16)";
  globeCtx.lineWidth = 1;

  for (let i = -2; i <= 2; i += 1) {
    const y = cy + (i * radius) / 3;
    const h = Math.sqrt(Math.max(0, radius * radius - (y - cy) * (y - cy)));
    globeCtx.beginPath();
    globeCtx.ellipse(cx, y, h, radius * 0.08, 0, 0, Math.PI * 2);
    globeCtx.stroke();
  }

  for (let i = -2; i <= 2; i += 1) {
    globeCtx.beginPath();
    globeCtx.ellipse(cx, cy, radius * Math.cos((i * Math.PI) / 10), radius, 0, 0, Math.PI * 2);
    globeCtx.stroke();
  }
}

function projectGlobePoint(cx, cy, radius, lonDeg, latDeg) {
  const lon = (lonDeg * Math.PI) / 180 + globeRotation;
  const lat = (latDeg * Math.PI) / 180;
  const x3 = Math.cos(lat) * Math.sin(lon);
  const y3 = Math.sin(lat);
  const z3 = Math.cos(lat) * Math.cos(lon);

  if (z3 < -0.08) return null;

  return {
    x: cx + x3 * radius,
    y: cy - y3 * radius,
    z: z3,
  };
}

function drawGlobeLink(cx, cy, radius, from, to) {
  const start = projectGlobePoint(cx, cy, radius, from.lon, from.lat);
  const end = projectGlobePoint(cx, cy, radius, to.lon, to.lat);

  if (!start || !end) return;

  const visibility = Math.min(Math.max(start.z, 0), Math.max(end.z, 0));
  const midX = (start.x + end.x) / 2;
  const midY = (start.y + end.y) / 2;
  const lift = Math.min(radius * 0.22, Math.hypot(end.x - start.x, end.y - start.y) * 0.22);

  globeCtx.save();
  globeCtx.globalAlpha = 0.16 + visibility * 0.34;
  globeCtx.strokeStyle = "rgba(52, 243, 226, 0.62)";
  globeCtx.lineWidth = from.group === "australia" && to.group === "australia" ? 1.8 : 1.15;
  globeCtx.shadowColor = "rgba(52, 243, 226, 0.4)";
  globeCtx.shadowBlur = 10;
  globeCtx.beginPath();
  globeCtx.moveTo(start.x, start.y);
  globeCtx.quadraticCurveTo(midX, midY - lift, end.x, end.y);
  globeCtx.stroke();
  globeCtx.restore();
}

function drawCapitalNode(cx, cy, radius, node) {
  const projected = projectGlobePoint(cx, cy, radius, node.lon, node.lat);

  if (!projected) return;

  const { x, y, z } = projected;
  const alpha = 0.45 + Math.max(0, z) * 0.55;
  const size = node.size ?? 3;

  globeCtx.save();
  globeCtx.globalAlpha = alpha;
  globeCtx.fillStyle = node.group === "australia" ? "#5ffff1" : "#34f3e2";
  globeCtx.shadowColor = "rgba(52, 243, 226, 0.8)";
  globeCtx.shadowBlur = node.group === "australia" ? 18 : 12;
  globeCtx.beginPath();
  globeCtx.arc(x, y, size, 0, Math.PI * 2);
  globeCtx.fill();

  if (node.group === "australia") {
    globeCtx.strokeStyle = "rgba(52, 243, 226, 0.32)";
    globeCtx.lineWidth = 1;
    globeCtx.beginPath();
    globeCtx.arc(x, y, size * 2.4, 0, Math.PI * 2);
    globeCtx.stroke();
  }

  globeCtx.restore();
}

function drawGlobe() {
  globeAnimationId = null;
  if (!globeCanvas || !globeCtx || !globeReady || !globeTexture) return;

  const size = globeCanvas.width;
  const cx = size / 2;
  const cy = size / 2;
  const radius = size * 0.39;
  const output = globeCtx.createImageData(size, size);
  const data = output.data;

  if (!reducedMotion.matches) {
    globeRotation += 0.006;
  }

  for (let y = 0; y < size; y += 1) {
    for (let x = 0; x < size; x += 1) {
      const dx = (x - cx) / radius;
      const dy = (y - cy) / radius;
      const d2 = dx * dx + dy * dy;
      const index = (y * size + x) * 4;

      if (d2 > 1) {
        data[index + 3] = 0;
        continue;
      }

      const z = Math.sqrt(1 - d2);
      const lat = Math.asin(-dy);
      const lon = Math.atan2(dx, z) - globeRotation;
      const land = sampleLand(globeTexture, lon, lat);
      const shade = 0.48 + z * 0.52;

      if (land) {
        data[index] = Math.floor(72 * shade);
        data[index + 1] = Math.floor(118 * shade);
        data[index + 2] = Math.floor(126 * shade);
        data[index + 3] = 210;
      } else {
        data[index] = Math.floor(6 * shade);
        data[index + 1] = Math.floor(24 * shade);
        data[index + 2] = Math.floor(36 * shade);
        data[index + 3] = 245;
      }
    }
  }

  globeCtx.clearRect(0, 0, size, size);
  globeCtx.putImageData(output, 0, 0);

  const glow = globeCtx.createRadialGradient(cx - radius * 0.35, cy - radius * 0.35, radius * 0.1, cx, cy, radius);
  glow.addColorStop(0, "rgba(52, 243, 226, 0.18)");
  glow.addColorStop(0.58, "rgba(52, 243, 226, 0.04)");
  glow.addColorStop(1, "rgba(0, 0, 0, 0.42)");
  globeCtx.fillStyle = glow;
  globeCtx.beginPath();
  globeCtx.arc(cx, cy, radius, 0, Math.PI * 2);
  globeCtx.fill();

  drawGlobeGrid(cx, cy, radius);

  const globeNodes = {
    brisbane: { lon: 153.03, lat: -27.47, size: 4.4, group: "australia" },
    canberra: { lon: 149.13, lat: -35.28, size: 4.8, group: "australia" },
    sydney: { lon: 151.21, lat: -33.87, size: 4.1, group: "australia" },
    perth: { lon: 115.86, lat: -31.95, size: 4.1, group: "australia" },
    washington: { lon: -77.04, lat: 38.9, size: 3.4, group: "five-eyes" },
    norfolk: { lon: -76.29, lat: 36.85, size: 2.8, group: "five-eyes" },
    sanDiego: { lon: -117.16, lat: 32.72, size: 2.8, group: "five-eyes" },
    honolulu: { lon: -157.86, lat: 21.31, size: 2.8, group: "five-eyes" },
    ottawa: { lon: -75.7, lat: 45.42, size: 3.2, group: "five-eyes" },
    london: { lon: -0.13, lat: 51.5, size: 3.4, group: "five-eyes" },
    portsmouth: { lon: -1.09, lat: 50.82, size: 2.7, group: "five-eyes" },
    wellington: { lon: 174.78, lat: -41.29, size: 3.2, group: "five-eyes" },
  };

  const globeLinks = [
    ["canberra", "brisbane"],
    ["canberra", "sydney"],
    ["canberra", "perth"],
    ["canberra", "wellington"],
    ["canberra", "london"],
    ["canberra", "washington"],
    ["canberra", "ottawa"],
    ["brisbane", "honolulu"],
    ["perth", "portsmouth"],
    ["sydney", "sanDiego"],
    ["washington", "norfolk"],
    ["london", "portsmouth"],
  ];

  for (const [fromKey, toKey] of globeLinks) {
    drawGlobeLink(cx, cy, radius, globeNodes[fromKey], globeNodes[toKey]);
  }

  for (const node of Object.values(globeNodes)) {
    drawCapitalNode(cx, cy, radius, node);
  }

  scheduleGlobeFrame();
}

setHeaderState();
resizeCanvas();
drawNetwork();
prepareGlobeTexture();

window.addEventListener("scroll", setHeaderState, { passive: true });
window.addEventListener("resize", () => {
  resizeCanvas();
  if (networkAnimationId) {
    cancelAnimationFrame(networkAnimationId);
    networkAnimationId = null;
  }
  drawNetwork();
});

toggle?.addEventListener("click", () => {
  const isOpen = header?.classList.toggle("is-open") ?? false;
  document.body.classList.toggle("nav-open", isOpen);
  toggle.setAttribute("aria-expanded", String(isOpen));
});

navLinks.forEach((link) => {
  link.addEventListener("click", closeNav);
});

document.addEventListener("visibilitychange", () => {
  if (!document.hidden) {
    if (networkAnimationId) {
      cancelAnimationFrame(networkAnimationId);
      networkAnimationId = null;
    }
    if (globeAnimationId) {
      cancelAnimationFrame(globeAnimationId);
      globeAnimationId = null;
    }
    drawNetwork();
    drawGlobe();
  }
});

reducedMotion.addEventListener("change", () => {
  if (networkAnimationId) {
    cancelAnimationFrame(networkAnimationId);
    networkAnimationId = null;
  }
  if (globeAnimationId) {
    cancelAnimationFrame(globeAnimationId);
    globeAnimationId = null;
  }
  drawNetwork();
  drawGlobe();
});

form?.addEventListener("submit", (event) => {
  event.preventDefault();
  const data = new FormData(form);
  const name = data.get("name")?.toString().trim() || "Website enquiry";
  const email = data.get("email")?.toString().trim() || "No email supplied";
  const interest = data.get("interest")?.toString().trim() || "Not specified";
  const message = data.get("message")?.toString().trim() || "No message supplied";
  const subject = encodeURIComponent(`JMO Solutions website enquiry from ${name}`);
  const body = encodeURIComponent(`Name: ${name}\nEmail: ${email}\nArea of interest: ${interest}\n\n${message}`);

  window.location.href = `mailto:info@jmosolutions.com.au?subject=${subject}&body=${body}`;
  formNote.textContent = "Your email client should open with the enquiry ready to send.";
  form.reset();
});
