import { useCallback, useEffect, useRef, useState } from 'react';

import {
  type BarkBattleConfig,
  DEFAULT_BARK_BATTLE_CONFIG,
} from '../application/BarkBattleConfig';
import { BarkBattleController } from '../application/BarkBattleController';
import type { MicrophoneFailureReason } from '../domain/BarkBattleTypes';
import {
  type BrowserMicrophoneSampler,
  startBrowserMicrophoneSampler,
} from '../infrastructure/BrowserMicrophoneInput';
import { BarkBattleHud } from './BarkBattleHud';
import { BarkBattleResultPanel } from './BarkBattleResultPanel';

type BarkBattleRuntimeShellProps = {
  title?: string;
};

type DebugEvent = {
  id: number;
  text: string;
};

const DEBUG_CONFIG_FIELDS: Array<{
  key: keyof Pick<
    BarkBattleConfig,
    | 'roundDurationMs'
    | 'countdownMs'
    | 'drawThreshold'
    | 'barkThreshold'
    | 'minBarkGapMs'
    | 'balanceFactor'
    | 'opponentBasePower'
  >;
  label: string;
  min: number;
  max: number;
  step: number;
}> = [
  { key: 'roundDurationMs', label: '局长(ms)', min: 1000, max: 60000, step: 1000 },
  { key: 'countdownMs', label: '倒计时(ms)', min: 0, max: 5000, step: 500 },
  { key: 'drawThreshold', label: '平局阈值', min: 0, max: 40, step: 1 },
  { key: 'barkThreshold', label: '叫声阈值', min: 0.1, max: 1, step: 0.05 },
  { key: 'minBarkGapMs', label: '叫声间隔(ms)', min: 100, max: 1200, step: 50 },
  { key: 'balanceFactor', label: '拉锯速度', min: 5, max: 80, step: 1 },
  { key: 'opponentBasePower', label: '对手基础力', min: 0, max: 1, step: 0.05 },
];

const MICROPHONE_FAILURE_REASONS = new Set<MicrophoneFailureReason>([
  'unsupported',
  'permission-denied',
  'non-secure-context',
  'not-found',
  'not-readable',
  'audio-context-blocked',
  'calibration-timeout',
  'calibration-sample-unreadable',
  'unknown',
]);

function isMicrophoneFailureReason(reason: unknown): reason is MicrophoneFailureReason {
  return typeof reason === 'string' && MICROPHONE_FAILURE_REASONS.has(reason as MicrophoneFailureReason);
}

export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: BarkBattleRuntimeShellProps) {
  const [config, setConfig] = useState(DEFAULT_BARK_BATTLE_CONFIG);
  const controllerRef = useRef<BarkBattleController | null>(null);
  if (!controllerRef.current) {
    controllerRef.current = new BarkBattleController(config);
  }
  const controller = controllerRef.current;
  const [snapshot, setSnapshot] = useState(() => controller.getSnapshot());
  const [particleText, setParticleText] = useState('');
  const [inputMode, setInputMode] = useState<'mock' | 'microphone'>('mock');
  const [liveInputVolume, setLiveInputVolume] = useState(0);
  const [isDebugExpanded, setIsDebugExpanded] = useState(false);
  const [playerPulseKey, setPlayerPulseKey] = useState(0);
  const [opponentPulseKey, setOpponentPulseKey] = useState(0);
  const [debugEvents, setDebugEvents] = useState<DebugEvent[]>([]);
  const heldRef = useRef(false);
  const lastPlayerBarkCountRef = useRef(0);
  const lastOpponentPowerRef = useRef(0);
  const debugEventIdRef = useRef(0);
  const microphoneSamplerRef = useRef<BrowserMicrophoneSampler | null>(null);

  const appendDebugEvent = useCallback((text: string) => {
    debugEventIdRef.current += 1;
    const event = { id: debugEventIdRef.current, text };
    setDebugEvents((current) => [event, ...current].slice(0, 5));
  }, []);

  const syncSnapshot = useCallback(() => {
    const nextSnapshot = controller.getSnapshot();
    if (nextSnapshot.player.barkCount > lastPlayerBarkCountRef.current) {
      setPlayerPulseKey((current) => current + 1);
      appendDebugEvent(`玩家叫声触发 #${nextSnapshot.player.barkCount} · 能量 ${Math.round(nextSnapshot.energy)}`);
    }
    if (nextSnapshot.phase === 'playing' && Math.abs(nextSnapshot.opponent.power - lastOpponentPowerRef.current) >= 0.08) {
      setOpponentPulseKey((current) => current + 1);
      appendDebugEvent(`对手反击强度 ${(nextSnapshot.opponent.power * 100).toFixed(0)}%`);
    }
    lastPlayerBarkCountRef.current = nextSnapshot.player.barkCount;
    lastOpponentPowerRef.current = nextSnapshot.opponent.power;
    setSnapshot(nextSnapshot);
  }, [appendDebugEvent, controller]);

  const stopMicrophone = useCallback(() => {
    microphoneSamplerRef.current?.stop();
    microphoneSamplerRef.current = null;
  }, []);

  const startMicrophone = useCallback(async () => {
    stopMicrophone();
    try {
      controller.startWithMockInput();
      const sampler = await startBrowserMicrophoneSampler((volume, atMs) => {
        setLiveInputVolume(volume);
        if (volume >= config.barkThreshold) {
          appendDebugEvent(`麦克风输入 ${(volume * 100).toFixed(0)}%`);
        }
        controller.submitInputSample(volume, atMs);
      });
      microphoneSamplerRef.current = sampler;
      setInputMode('microphone');
      appendDebugEvent('真实麦克风已开启');
      syncSnapshot();
    } catch (error) {
      const reason = error && typeof error === 'object' && 'reason' in error ? error.reason : 'unknown';
      const failureReason = isMicrophoneFailureReason(reason) ? reason : 'unknown';
      controller.failMicrophone(failureReason);
      appendDebugEvent(`麦克风不可用：${failureReason}`);
      syncSnapshot();
    }
  }, [appendDebugEvent, config.barkThreshold, controller, stopMicrophone, syncSnapshot]);

  useEffect(() => stopMicrophone, [stopMicrophone]);

  useEffect(() => {
    controller.updateConfig(config);
    syncSnapshot();
  }, [config, controller, syncSnapshot]);

  useEffect(() => {
    const timer = window.setInterval(() => {
      controller.tick(100);
      if (inputMode === 'mock') {
        if (heldRef.current) {
          controller.submitMockSample(0.88);
        } else {
          controller.submitMockSample(0.12);
          setLiveInputVolume(0);
        }
      }
      syncSnapshot();
    }, 100);
    return () => window.clearInterval(timer);
  }, [controller, inputMode, syncSnapshot]);

  const restart = () => {
    heldRef.current = false;
    stopMicrophone();
    setInputMode('mock');
    setLiveInputVolume(0);
    controller.restart();
    setParticleText('');
    setDebugEvents([]);
    lastPlayerBarkCountRef.current = 0;
    lastOpponentPowerRef.current = 0;
    syncSnapshot();
  };

  const startMock = () => {
    stopMicrophone();
    setInputMode('mock');
    setLiveInputVolume(0);
    controller.startWithMockInput();
    appendDebugEvent('开始 mock 对局（不会请求浏览器麦克风权限）');
    syncSnapshot();
  };

  const finishNow = () => {
    heldRef.current = false;
    stopMicrophone();
    controller.finishNow();
    appendDebugEvent('人工结束对局');
    syncSnapshot();
  };

  const bark = () => {
    controller.forcePlayerBark(0.9);
    syncSnapshot();
    setParticleText('汪！');
    window.setTimeout(() => setParticleText(''), 680);
  };

  return (
    <main className="bark-battle-runtime" aria-label={title}>
      <BarkBattleHud
        snapshot={snapshot}
        playerPulseKey={playerPulseKey}
        opponentPulseKey={opponentPulseKey}
        onStartMicrophone={startMicrophone}
        onMockBark={bark}
        onMockQuiet={() => {
          heldRef.current = false;
        }}
        onRestart={restart}
      />
      <aside className={`bark-battle-debug-panel${isDebugExpanded ? ' bark-battle-debug-panel--expanded' : ''}`} aria-label="调试面板">
        <header>
          <strong>调试面板</strong>
          <button
            type="button"
            className="bark-battle-debug-panel__toggle"
            aria-expanded={isDebugExpanded}
            onClick={() => setIsDebugExpanded((current) => !current)}
          >
            {isDebugExpanded ? '收起' : '展开'}
          </button>
          <span>{snapshot.phase}</span>
        </header>
        <div className="bark-battle-debug-panel__body">
          <div className="bark-battle-debug-panel__controls">
            <button type="button" onClick={startMock}>开始</button>
            <button type="button" onClick={finishNow}>结束</button>
            <button type="button" onClick={restart}>重置</button>
          </div>
          <div className="bark-battle-debug-metrics" aria-label="触发反馈">
            <span className="bark-battle-debug-metrics__wide">输入模式：{inputMode === 'microphone' ? '真实麦克风' : 'Mock 输入'}</span>
            <span>实时音量：{(liveInputVolume * 100).toFixed(0)}%</span>
            <span>采样时钟：{controller.getSampleClockMs()}ms</span>
            <span>玩家触发：{snapshot.player.barkCount}</span>
            <span>玩家强度：{(snapshot.player.power * 100).toFixed(0)}%</span>
            <span>对手强度：{(snapshot.opponent.power * 100).toFixed(0)}%</span>
            <span>能量：{Math.round(snapshot.energy)}</span>
          </div>
          <ol className="bark-battle-debug-events" aria-label="触发日志">
            {debugEvents.length ? debugEvents.map((event) => <li key={event.id}>{event.text}</li>) : <li>等待输入触发</li>}
          </ol>
          {DEBUG_CONFIG_FIELDS.map((field) => (
            <label key={field.key}>
              <span>{field.label}</span>
              <input
                aria-label={field.label}
                type="range"
                min={field.min}
                max={field.max}
                step={field.step}
                value={config[field.key]}
                onChange={(event) => {
                  const value = Number(event.currentTarget.value);
                  setConfig((current) => ({ ...current, [field.key]: value }));
                }}
              />
              <output>{config[field.key]}</output>
            </label>
          ))}
        </div>
      </aside>
      {particleText ? <div className="bark-battle-particles">{particleText}</div> : null}
      {snapshot.result ? <BarkBattleResultPanel result={snapshot.result} onRestart={restart} /> : null}
    </main>
  );
}
