/** AD-721: ParametricAvatar test — verifies tint resolution + signal-driven animation knobs. */
import { describe, it, expect, vi, beforeEach } from 'vitest';

// Hoisted mocks for @react-three/fiber + onSpeechEvent.
const fiberMocks = vi.hoisted(() => {
  const frameCallbacks: ((state: any, delta: number) => void)[] = [];
  return {
    frameCallbacks,
    useFrameImpl: (fn: any) => { frameCallbacks.push(fn); },
    listeners: new Set<(e: any) => void>(),
  };
});

vi.mock('@react-three/fiber', () => ({
  useFrame: (fn: any) => fiberMocks.useFrameImpl(fn),
  Canvas: ({ children }: any) => children,
}));

vi.mock('../audio/voice', () => ({
  getServerPiperVoices: vi.fn(async () => null),
  onSpeechEvent: (fn: any) => {
    fiberMocks.listeners.add(fn);
    return () => { fiberMocks.listeners.delete(fn); };
  },
}));

vi.mock('../audio/speechAmplitude', () => ({
  _attachAnalyserOrSchedule: () => ({
    frequencyBinCount: 32,
    getByteFrequencyData: (buf: Uint8Array) => buf.fill(128),
  }),
}));

import { render } from '@testing-library/react';
import { ParametricAvatar } from '../components/profile/ParametricAvatar';
import type { AgentSignals } from '../components/profile/avatarSignals';

const idleSignals: AgentSignals = { trust_delta: 0, load: 0, working_state: 'idle', tier3_alert: false };

beforeEach(() => {
  fiberMocks.frameCallbacks.length = 0;
  fiberMocks.listeners.clear();
});

describe('AD-721 ParametricAvatar', () => {
  it('mounts and registers a useFrame callback', () => {
    render(<ParametricAvatar tint="#d0a030" signals={idleSignals} agentId="agent-007" />);
    expect(fiberMocks.frameCallbacks.length).toBeGreaterThanOrEqual(1);
  });

  it('subscribes to onSpeechEvent on mount and unsubscribes on unmount', () => {
    const { unmount } = render(<ParametricAvatar tint="#50b0a0" signals={idleSignals} agentId="agent-007" />);
    expect(fiberMocks.listeners.size).toBe(1);
    unmount();
    expect(fiberMocks.listeners.size).toBe(0);
  });

  it('mouth amplitude updates only for matching agent_id', () => {
    render(<ParametricAvatar tint="#d0a030" signals={idleSignals} agentId="agent-007" />);
    const fakeUtterance = { text: 'hello', rate: 1 } as any;
    // Different agent_id — listener early-returns and does not enable speaking state.
    let threwForOther = false;
    try {
      for (const fn of fiberMocks.listeners) {
        fn({ type: 'start', agent_id: 'other', utterance: fakeUtterance });
      }
    } catch { threwForOther = true; }
    expect(threwForOther).toBe(false);
    // Matching agent_id — listener accepts; still must not throw.
    let threwForMatch = false;
    try {
      for (const fn of fiberMocks.listeners) {
        fn({ type: 'start', agent_id: 'agent-007', utterance: fakeUtterance });
        fn({ type: 'end', agent_id: 'agent-007', utterance: fakeUtterance });
      }
    } catch { threwForMatch = true; }
    expect(threwForMatch).toBe(false);
  });
});
