"use client";

import { useEffect, useRef } from "react";

// Ambient network-traffic background for the login screen.
// Procedural node graph biased to the edges, with pulses that travel edges
// like packets. Center-right stays calm so the sign-in card reads cleanly.

interface Node {
    x: number;
    y: number;
    r: number;
    flash: number; // 0..1, brief brighten when a packet arrives
}

interface Edge {
    a: number;
    b: number;
    len: number;
}

interface Pulse {
    edge: number;
    dir: 1 | -1; // travel a->b or b->a
    t: number; // 0..1 progress
    speed: number; // progress per second
    delay: number; // seconds until it starts
    life: number; // seconds since spawn
}

/*
 * Drawn from the shared tokens rather than read from them: this is canvas, so
 * the values have to be literals. They are the light-theme accent falling to
 * the app ground, with packets in the dark-theme accent the same two blues
 * the marketing site's hero is built on.
 */
const BG_TOP = "#0b2a58"; // --accent, light
const BG_BOTTOM = "#071628"; // --ground, dark
const NODE_COLOR = "113, 135, 159"; // --ink-3, rgb parts for alpha use
const LINE_COLOR = "30, 56, 85"; // --rule
const PULSE_COLOR = "91, 155, 232"; // --accent, dark
const PULSE_CORE = "228, 236, 246"; // --ink, a white-hot centre

const MAX_DIST = 190; // connect nodes within this px
const NODE_DENSITY = 1 / 22000; // nodes per px^2
const CALM_CENTER = { cx: 0.62, cy: 0.5, rx: 0.28, ry: 0.34 }; // normalised ellipse to avoid

export function NetworkBackground() {
    const canvasRef = useRef<HTMLCanvasElement | null>(null);

    useEffect(() => {
        const canvas: HTMLCanvasElement | null = canvasRef.current;
        if (!canvas) return;
        const ctx2d = canvas.getContext("2d");
        if (!ctx2d) return;
        const cvs: HTMLCanvasElement = canvas;
        const ctx: CanvasRenderingContext2D = ctx2d;

        const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

        let nodes: Node[] = [];
        let edges: Edge[] = [];
        let pulses: Pulse[] = [];
        let width = 0;
        let height = 0;
        let dpr = 1;
        let raf = 0;
        let lastTime = 0;
        let resizeTimer: ReturnType<typeof setTimeout> | undefined;

        // Distance (0..1+) into the calm zone; <1 means inside the avoid ellipse.
        function calmFactor(nx: number, ny: number) {
            const { cx, cy, rx, ry } = CALM_CENTER;
            const dx = (nx - cx) / rx;
            const dy = (ny - cy) / ry;
            return Math.sqrt(dx * dx + dy * dy);
        }

        function build() {
            nodes = [];
            edges = [];
            pulses = [];

            const target = Math.round(width * height * NODE_DENSITY);
            const count = Math.max(24, Math.min(140, target));

            let guard = 0;
            while (nodes.length < count && guard < count * 40) {
                guard++;
                // Bias toward edges: pull samples away from centre.
                const ex = Math.pow(Math.random(), 0.65);
                const ey = Math.pow(Math.random(), 0.65);
                const nx = Math.random() < 0.5 ? ex * 0.5 : 1 - ex * 0.5;
                const ny = Math.random() < 0.5 ? ey * 0.5 : 1 - ey * 0.5;

                const calm = calmFactor(nx, ny);
                // Reject most nodes inside the calm zone, a few survive for texture.
                if (calm < 1 && Math.random() > calm * 0.35) continue;

                nodes.push({
                    x: nx * width,
                    y: ny * height,
                    r: 2 + Math.random() * 2,
                    flash: 0,
                });
            }

            // Connect nearby nodes, capped degree so it stays sparse.
            const degree = new Array(nodes.length).fill(0);
            for (let i = 0; i < nodes.length; i++) {
                for (let j = i + 1; j < nodes.length; j++) {
                    if (degree[i] >= 3) break;
                    if (degree[j] >= 3) continue;
                    const dx = nodes[i].x - nodes[j].x;
                    const dy = nodes[i].y - nodes[j].y;
                    const len = Math.hypot(dx, dy);
                    if (len > MAX_DIST) continue;
                    // Skip edges that cut across the calm centre.
                    const mx = (nodes[i].x + nodes[j].x) / 2 / width;
                    const my = (nodes[i].y + nodes[j].y) / 2 / height;
                    if (calmFactor(mx, my) < 0.9) continue;
                    edges.push({ a: i, b: j, len });
                    degree[i]++;
                    degree[j]++;
                }
            }

            // Seed one pulse per few edges, staggered.
            for (let e = 0; e < edges.length; e++) {
                if (Math.random() < 0.55) pulses.push(spawnPulse(e, true));
            }
        }

        function spawnPulse(edge: number, initial = false): Pulse {
            return {
                edge,
                dir: Math.random() < 0.5 ? 1 : -1,
                t: 0,
                speed: 0.18 + Math.random() * 0.22, // ~2.5-5.5s per edge
                delay: initial ? Math.random() * 8 : Math.random() * 6,
                life: 0,
            };
        }

        function resize() {
            dpr = Math.min(window.devicePixelRatio || 1, 2);
            width = window.innerWidth;
            height = window.innerHeight;
            cvs.width = Math.floor(width * dpr);
            cvs.height = Math.floor(height * dpr);
            cvs.style.width = `${width}px`;
            cvs.style.height = `${height}px`;
            ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
            build();
        }

        function drawBackground() {
            const g = ctx.createLinearGradient(0, 0, width, height);
            g.addColorStop(0, BG_TOP);
            g.addColorStop(1, BG_BOTTOM);
            ctx.fillStyle = g;
            ctx.fillRect(0, 0, width, height);
        }

        function drawStatic() {
            drawBackground();
            ctx.lineWidth = 1;
            for (const edge of edges) {
                const a = nodes[edge.a];
                const b = nodes[edge.b];
                ctx.strokeStyle = `rgba(${LINE_COLOR}, 0.2)`;
                ctx.beginPath();
                ctx.moveTo(a.x, a.y);
                ctx.lineTo(b.x, b.y);
                ctx.stroke();
            }
            for (const n of nodes) {
                ctx.fillStyle = `rgba(${NODE_COLOR}, 0.5)`;
                ctx.beginPath();
                ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function frame(now: number) {
            const dt = lastTime ? Math.min((now - lastTime) / 1000, 0.05) : 0;
            lastTime = now;

            drawBackground();

            // Edges.
            ctx.lineWidth = 1;
            for (const edge of edges) {
                const a = nodes[edge.a];
                const b = nodes[edge.b];
                ctx.strokeStyle = `rgba(${LINE_COLOR}, 0.22)`;
                ctx.beginPath();
                ctx.moveTo(a.x, a.y);
                ctx.lineTo(b.x, b.y);
                ctx.stroke();
            }

            // Pulses.
            for (let i = 0; i < pulses.length; i++) {
                const p = pulses[i];
                p.life += dt;
                if (p.life < p.delay) continue;
                p.t += p.speed * dt;

                const edge = edges[p.edge];
                if (!edge) {
                    pulses[i] = spawnPulse(Math.floor(Math.random() * edges.length));
                    continue;
                }
                const a = nodes[edge.a];
                const b = nodes[edge.b];
                const from = p.dir === 1 ? a : b;
                const to = p.dir === 1 ? b : a;

                if (p.t >= 1) {
                    to.flash = 1; // packet arrived
                    pulses[i] = spawnPulse(p.edge);
                    continue;
                }

                const x = from.x + (to.x - from.x) * p.t;
                const y = from.y + (to.y - from.y) * p.t;
                // Fade in over first 15%, out over last 25%.
                const fade = Math.min(p.t / 0.15, 1) * Math.min((1 - p.t) / 0.25, 1);
                const alpha = 0.85 * fade;

                ctx.save();
                ctx.shadowBlur = 10;
                ctx.shadowColor = `rgba(${PULSE_COLOR}, ${alpha})`;
                ctx.fillStyle = `rgba(${PULSE_COLOR}, ${alpha})`;
                ctx.beginPath();
                ctx.arc(x, y, 2.2, 0, Math.PI * 2);
                ctx.fill();
                ctx.fillStyle = `rgba(${PULSE_CORE}, ${alpha})`;
                ctx.beginPath();
                ctx.arc(x, y, 1.1, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            }

            // Nodes, brighter while flashing.
            for (const n of nodes) {
                if (n.flash > 0) n.flash = Math.max(0, n.flash - dt * 2);
                const base = 0.45 + n.flash * 0.5;
                if (n.flash > 0) {
                    ctx.save();
                    ctx.shadowBlur = 8 * n.flash;
                    ctx.shadowColor = `rgba(${PULSE_COLOR}, ${n.flash})`;
                    ctx.fillStyle = `rgba(${PULSE_COLOR}, ${0.4 + n.flash * 0.5})`;
                    ctx.beginPath();
                    ctx.arc(n.x, n.y, n.r + n.flash, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.restore();
                } else {
                    ctx.fillStyle = `rgba(${NODE_COLOR}, ${base})`;
                    ctx.beginPath();
                    ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
                    ctx.fill();
                }
            }

            raf = requestAnimationFrame(frame);
        }

        function onResize() {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(() => {
                resize();
                if (reducedMotion) drawStatic();
            }, 150);
        }

        resize();
        if (reducedMotion) {
            drawStatic();
        } else {
            raf = requestAnimationFrame(frame);
        }
        window.addEventListener("resize", onResize);

        return () => {
            cancelAnimationFrame(raf);
            clearTimeout(resizeTimer);
            window.removeEventListener("resize", onResize);
        };
    }, []);

    return <canvas ref={canvasRef} aria-hidden="true" className="pointer-events-none fixed inset-0 -z-10 h-full w-full" />;
}
