/**
 * PixelOfficeCrew — Multi-agent pixel art visualization for Command Post.
 * Shows each registered agent as a pixel robot at their own desk.
 * Status-driven animations: idle, active, paused, error, stopped.
 */
import { useRef, useEffect } from 'react';
import type { RegisteredAgent, CPActivityEntry } from '@/api/types';

type PixelState = 'idle' | 'active' | 'paused' | 'error' | 'stopped';

type ThemePalette = {
  bg: string;
  bgTertiary: string;
  border: string;
  borderLight: string;
  textMuted: string;
  textSecondary: string;
  accent: string;
  accentLight: string;
  success: string;
  warning: string;
  error: string;
};

const COLOR_FIELDS: Record<PixelState, keyof ThemePalette> = {
  idle: 'textSecondary',
  active: 'success',
  paused: 'warning',
  error: 'error',
  stopped: 'borderLight',
};

const ROLE_ICONS: Record<string, string> = {
  ceo: 'C', manager: 'M', engineer: 'E', researcher: 'R', general: 'G',
};

interface Props {
  agents: RegisteredAgent[];
  activity: CPActivityEntry[];
}

export function PixelOfficeCrew({ agents, activity }: Props) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const agentsRef = useRef(agents);
  const activityRef = useRef(activity);
  agentsRef.current = agents;
  activityRef.current = activity;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const dpr = window.devicePixelRatio || 1;
    let animId: number;
    let frame = 0;

    // v6.1.0-beta.24 — Canvas 2D `fillStyle` and `strokeStyle` need concrete
    // color strings. Pre-beta.24 this file passed unresolved CSS variables
    // into fillStyle/strokeStyle calls; every one silently fell back to
    // black, which is why the pixel office crew rendered as a black square
    // on a dark background (visible in Tony's screenshot 2026-05-17).
    //
    // Helper: resolve a CSS variable from :root once per frame, with a
    // hard-coded fallback that matches the default zinc-indigo palette in
    // ui/src/styles/globals.css. Re-reads each frame so theme picker
    // changes (Office/Workshop/Observatory) take effect immediately.
    const resolveTheme = (): ThemePalette => {
      const root = document.documentElement;
      const cs = getComputedStyle(root);
      const v = (themeName: string | null, colorName: string, fallback: string) => {
        const themeVal = themeName ? cs.getPropertyValue(themeName).trim() : '';
        if (themeVal) return themeVal;
        const colorVal = cs.getPropertyValue(colorName).trim();
        return colorVal || fallback;
      };

      // Theme picker mapping:
      // bg -> --theme-bg-base; bgTertiary -> --theme-bg-grain;
      // border -> --theme-metal-dark; borderLight -> --theme-metal;
      // textMuted -> --theme-ink-soft; textSecondary -> --theme-paper-fg;
      // accent -> --theme-accent. The theme palette has no accent-light,
      // success, warning, or error equivalents, so those intentionally fall
      // through to the static --color-* tokens before using hex defaults.
      return {
        bg: v('--theme-bg-base', '--color-bg', '#09090b'),
        bgTertiary: v('--theme-bg-grain', '--color-bg-tertiary', '#27272a'),
        border: v('--theme-metal-dark', '--color-border', '#3f3f46'),
        borderLight: v('--theme-metal', '--color-border-light', '#52525b'),
        textMuted: v('--theme-ink-soft', '--color-text-muted', '#71717a'),
        textSecondary: v('--theme-paper-fg', '--color-text-secondary', '#a1a1aa'),
        accent: v('--theme-accent', '--color-accent', '#6366f1'),
        accentLight: v(null, '--color-accent-light', '#a5b4fc'),
        success: v(null, '--color-success', '#22c55e'),
        warning: v(null, '--color-warning', '#f59e0b'),
        error: v(null, '--color-error', '#ef4444'),
      };
    };

    const resize = () => {
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    window.addEventListener('resize', resize);

    const draw = () => {
      frame++;
      const t = frame / 15;
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      const agentsList = agentsRef.current;
      const recentActivity = activityRef.current;
      // Resolve theme colors once per frame so the topbar theme picker
      // (Office / Workshop / Observatory) re-skins the canvas live.
      const theme = resolveTheme();

      // Clear
      ctx.fillStyle = theme.bg;
      ctx.fillRect(0, 0, w, h);

      // Floor grid
      ctx.strokeStyle = '#141418';
      ctx.lineWidth = 0.5;
      for (let i = 0; i < w; i += 24) { ctx.beginPath(); ctx.moveTo(i, h * 0.72); ctx.lineTo(i, h); ctx.stroke(); }
      for (let j = h * 0.72; j < h; j += 24) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(w, j); ctx.stroke(); }

      if (agentsList.length === 0) {
        // Empty state
        ctx.fillStyle = theme.bgTertiary;
        ctx.font = '12px -apple-system, BlinkMacSystemFont, sans-serif';
        ctx.textAlign = 'center';
        ctx.fillText('No agents registered', w / 2, h / 2 - 10);
        ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif';
        ctx.fillStyle = theme.border;
        ctx.fillText('Spawn agents to see them here', w / 2, h / 2 + 10);
        ctx.textAlign = 'start';
        animId = requestAnimationFrame(draw);
        return;
      }

      // Calculate layout — agents evenly spaced
      const count = Math.min(agentsList.length, 8); // max 8 visible
      const slotW = Math.min(120, (w - 20) / count);
      const startX = (w - slotW * count) / 2 + slotW / 2;

      for (let i = 0; i < count; i++) {
        const agent = agentsList[i];
        const cx = startX + i * slotW;
        const cy = h * 0.55;
        const state = (agent.status === 'active' ? 'active' : agent.status) as PixelState;
        const color = theme[COLOR_FIELDS[state] || COLOR_FIELDS.idle];
        const bobY = Math.sin(t * 0.8 + i * 1.5) * 1.5;

        // Is this agent doing something? Check recent activity
        const isWorking = recentActivity.some(a =>
          a.agentId === agent.id && (Date.now() - new Date(a.timestamp).getTime()) < 30000
        );

        // Desk
        const deskW = Math.min(70, slotW - 10);
        const deskH = 10;
        const deskX = cx - deskW / 2;
        const deskY = cy + 16;
        ctx.fillStyle = theme.border;
        ctx.fillRect(deskX, deskY, deskW, deskH);
        ctx.fillStyle = theme.borderLight;
        ctx.fillRect(deskX + 1, deskY + 1, deskW - 2, deskH - 2);
        // Legs
        ctx.fillStyle = theme.border;
        ctx.fillRect(deskX + 3, deskY + deskH, 3, 14);
        ctx.fillRect(deskX + deskW - 6, deskY + deskH, 3, 14);

        // Monitor
        const monW = 26;
        const monH = 18;
        const monX = cx - monW / 2;
        const monY = deskY - monH;
        ctx.fillStyle = theme.bgTertiary;
        ctx.fillRect(monX, monY, monW, monH);
        // Hex-color alpha overlay (e.g. '#ef4444' + '30' = '#ef444430') is
        // valid #RRGGBBAA — required because the previous CSS-token + alpha
        // string was rejected by canvas and silently fell to default black.
        const screenColor = state === 'error' ? theme.error : state === 'active' ? theme.success : theme.accent;
        ctx.fillStyle = screenColor + '30';
        ctx.fillRect(monX + 2, monY + 2, monW - 4, monH - 4);
        // Screen lines when active
        if (state === 'active' || isWorking) {
          ctx.fillStyle = screenColor + '60';
          const lines = 2 + Math.floor(t % 3);
          for (let l = 0; l < lines; l++) {
            const lw = 6 + Math.sin(t * 2 + l + i) * 5;
            ctx.fillRect(monX + 3, monY + 3 + l * 4, lw, 1.5);
          }
        }
        // Stand
        ctx.fillStyle = theme.border;
        ctx.fillRect(cx - 2, deskY - 1, 4, 3);

        // Chair
        ctx.fillStyle = '#1e1e22';
        ctx.fillRect(cx - 10, cy + 10 + bobY, 20, 8);
        ctx.fillRect(cx - 11, cy - 2 + bobY, 3, 20);

        // Body
        ctx.fillStyle = theme.border;
        ctx.fillRect(cx - 7, cy + bobY, 14, 12);

        // Head
        const headBob = state === 'active' && isWorking ? Math.sin(t * 1.5 + i) * 1.5 : 0;
        ctx.fillStyle = theme.borderLight;
        ctx.fillRect(cx - 6, cy - 10 + bobY + headBob, 12, 10);

        // Eyes
        const blinkPhase = Math.sin(t * 0.3 + i * 2);
        const eyeH = blinkPhase > 0.95 ? 1 : 2;
        ctx.fillStyle = color;
        if (state === 'stopped') {
          // X eyes
          ctx.fillRect(cx - 4, cy - 7 + bobY + headBob, 2, 1);
          ctx.fillRect(cx - 3, cy - 6 + bobY + headBob, 2, 1);
          ctx.fillRect(cx + 1, cy - 7 + bobY + headBob, 2, 1);
          ctx.fillRect(cx + 2, cy - 6 + bobY + headBob, 2, 1);
        } else {
          ctx.fillRect(cx - 4, cy - 7 + bobY + headBob, 2, eyeH);
          ctx.fillRect(cx + 2, cy - 7 + bobY + headBob, 2, eyeH);
        }

        // Antenna
        ctx.fillStyle = theme.borderLight;
        ctx.fillRect(cx - 1, cy - 14 + bobY + headBob, 2, 4);
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(cx, cy - 15 + bobY + headBob, 2, 0, Math.PI * 2);
        ctx.fill();
        // Antenna glow for active
        if (state === 'active') {
          ctx.globalAlpha = 0.3 + Math.sin(t * 3 + i) * 0.2;
          ctx.beginPath();
          ctx.arc(cx, cy - 15 + bobY + headBob, 5, 0, Math.PI * 2);
          ctx.fill();
          ctx.globalAlpha = 1;
        }

        // Arms
        if (state === 'active' && isWorking) {
          const armL = Math.sin(t * 6 + i) * 2;
          const armR = Math.sin(t * 6 + i + Math.PI) * 2;
          ctx.fillStyle = theme.border;
          ctx.fillRect(cx - 11, cy + 3 + bobY + armL, 4, 3);
          ctx.fillRect(cx + 7, cy + 3 + bobY + armR, 4, 3);
        } else {
          ctx.fillStyle = theme.border;
          ctx.fillRect(cx - 10, cy + 5 + bobY, 3, 6);
          ctx.fillRect(cx + 7, cy + 5 + bobY, 3, 6);
        }

        // Status particles
        if (state === 'active' && isWorking) {
          for (let p = 0; p < 2; p++) {
            const angle = t * 1.5 + p * Math.PI + i;
            const radius = 14 + Math.sin(t + p) * 4;
            const px = cx + Math.cos(angle) * radius;
            const py = cy - 3 + Math.sin(angle) * radius * 0.4 + bobY;
            ctx.globalAlpha = 0.25 + Math.sin(t * 2 + p) * 0.15;
            ctx.fillStyle = color;
            ctx.beginPath();
            ctx.arc(px, py, 1, 0, Math.PI * 2);
            ctx.fill();
          }
          ctx.globalAlpha = 1;
        }

        // Error sparks
        if (state === 'error') {
          for (let p = 0; p < 3; p++) {
            const sparkX = cx + Math.sin(t * 4 + p * 2) * 12;
            const sparkY = cy - 5 + Math.cos(t * 3 + p) * 8 + bobY;
            ctx.globalAlpha = 0.4 + Math.sin(t + p) * 0.3;
            ctx.fillStyle = theme.error;
            ctx.fillRect(sparkX - 1, sparkY - 1, 2, 2);
          }
          ctx.globalAlpha = 1;
        }

        // Name label
        ctx.fillStyle = state === 'stopped' ? theme.border : theme.textMuted;
        ctx.font = '9px -apple-system, BlinkMacSystemFont, sans-serif';
        ctx.textAlign = 'center';
        // v4.8.4: fit the agent's name into the desk width by measuring,
        // rather than always chopping at 10 chars. "TITAN Primary" fit
        // fine on a slightly wider desk; chopping produced "TITAN Pri…".
        const maxLabelWidth = Math.max(44, deskW + 14);
        let label = agent.name;
        if (ctx.measureText(label).width > maxLabelWidth) {
            // Shorten progressively until it fits, then add an ellipsis.
            while (label.length > 1 && ctx.measureText(label + '…').width > maxLabelWidth) {
                label = label.slice(0, -1);
            }
            label = label.trimEnd() + '…';
        }
        ctx.fillText(label, cx, deskY + deskH + 24);

        // Role badge
        const roleChar = ROLE_ICONS[agent.role] || 'G';
        ctx.fillStyle = color + '60';
        ctx.font = 'bold 7px -apple-system, BlinkMacSystemFont, sans-serif';
        ctx.fillText(roleChar, cx, deskY + deskH + 33);

        ctx.textAlign = 'start';
      }

      // Title
      ctx.fillStyle = theme.bgTertiary;
      ctx.font = 'bold 9px -apple-system, BlinkMacSystemFont, sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('COMMAND POST', w / 2, h - 6);
      ctx.textAlign = 'start';

      // Activity ticker at top
      if (recentActivity.length > 0) {
        const latest = recentActivity[recentActivity.length - 1];
        const age = Date.now() - new Date(latest.timestamp).getTime();
        if (age < 60000) {
          ctx.fillStyle = theme.accent + '40';
          ctx.fillRect(0, 0, w, 16);
          ctx.fillStyle = theme.accentLight;
          ctx.font = '8px -apple-system, BlinkMacSystemFont, sans-serif';
          const tickerText = latest.message.length > 60 ? latest.message.slice(0, 57) + '...' : latest.message;
          ctx.textAlign = 'center';
          ctx.fillText(tickerText, w / 2, 11);
          ctx.textAlign = 'start';
        }
      }

      animId = requestAnimationFrame(draw);
    };

    draw();
    return () => {
      cancelAnimationFrame(animId);
      window.removeEventListener('resize', resize);
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      className="w-full rounded-xl border border-border"
      style={{ height: 220, imageRendering: 'pixelated' }}
    />
  );
}
