import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:it0_app/l10n/app_localizations.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
import '../../../../core/config/api_endpoints.dart';
import '../../../../core/config/app_config.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../chat/domain/entities/chat_message.dart';
import '../../../settings/presentation/providers/settings_providers.dart';

/// Tracks the current state of the voice call.
enum _CallPhase { ringing, connecting, active, ended }

class AgentCallPage extends ConsumerStatefulWidget {
  const AgentCallPage({super.key});

  @override
  ConsumerState<AgentCallPage> createState() => _AgentCallPageState();
}

class _AgentCallPageState extends ConsumerState<AgentCallPage>
    with TickerProviderStateMixin {
  _CallPhase _phase = _CallPhase.ringing;
  String? _errorMessage;

  // LiveKit
  Room? _room;
  EventsListener<RoomEvent>? _listener;

  // Call duration
  final Stopwatch _stopwatch = Stopwatch();
  Timer? _durationTimer;
  String _durationLabel = '00:00';

  // Waveform
  late AnimationController _waveController;
  final List<double> _waveHeights = List.generate(20, (_) => 0.3);
  Timer? _waveTimer;

  // Agent state — read from the "lk.agent.state" participant attribute that
  // LiveKit AgentSession publishes automatically. Values:
  //   "initializing" → agent starting up
  //   "listening"    → waiting for user speech
  //   "thinking"     → STT done, LLM processing (show thinking animation)
  //   "speaking"     → TTS playing response
  // See ParticipantAttributesChanged listener in _acceptCall().
  String _agentState = '';
  late AnimationController _thinkingController;

  // Mute & speaker state
  bool _isMuted = false;
  bool _isSpeaker = true;

  // Reconnection state
  bool _isReconnecting = false;
  int _reconnectAttempt = 0;

  // Prevent double-actions
  bool _userEndedCall = false;

  // Text input (mixed-mode: text + attachments during voice call)
  final _textController = TextEditingController();
  bool _isInputExpanded = false;
  bool _isSending = false;
  final List<ChatAttachment> _pendingAttachments = [];
  final List<_CallMessage> _messages = [];
  final ScrollController _scrollController = ScrollController();

  // PTT (Push-to-Talk) mode
  bool _isPttMode = true;
  bool _pttRecording = false;
  final Stopwatch _pttStopwatch = Stopwatch();
  Timer? _pttTimer;

  @override
  void initState() {
    super.initState();
    _waveController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    _thinkingController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1200),
    )..repeat();
  }

  // ---------------------------------------------------------------------------
  // Call lifecycle
  // ---------------------------------------------------------------------------

  /// Accept call: get LiveKit token, connect to room, publish mic.
  Future<void> _acceptCall() async {
    setState(() {
      _phase = _CallPhase.connecting;
      _errorMessage = null;
    });

    try {
      final dio = ref.read(dioClientProvider);
      final config = ref.read(appConfigProvider);

      // 1. Get LiveKit token from backend (with voice preferences)
      final voiceSettings = ref.read(settingsProvider);
      final response = await dio.post(
        ApiEndpoints.livekitToken,
        data: {
          if (voiceSettings.ttsVoice.isNotEmpty) 'tts_voice': voiceSettings.ttsVoice,
          if (voiceSettings.ttsStyle.isNotEmpty) 'tts_style': voiceSettings.ttsStyle,
          'engine_type': voiceSettings.engineType,
        },
      );
      final data = response.data as Map<String, dynamic>;
      final token = data['token'] as String;
      final livekitUrl = data['livekit_url'] as String? ?? config.livekitUrl;

      // 2. Create and connect LiveKit room
      _room = Room();

      // 3. Set up event listeners
      _listener = _room!.createListener();
      _listener!
        ..on<TrackSubscribedEvent>((event) {
          // Agent's audio track subscribed — playback is automatic via WebRTC
          _startWaveformUpdates();
        })
        ..on<TrackUnsubscribedEvent>((event) {
          // Agent's audio track removed
        })
        ..on<RoomReconnectingEvent>((event) {
          if (mounted) {
            setState(() {
              _isReconnecting = true;
              _reconnectAttempt = 0;
            });
          }
        })
        ..on<RoomAttemptReconnectEvent>((event) {
          if (mounted) {
            setState(() {
              _reconnectAttempt = event.attempt;
            });
          }
        })
        ..on<RoomReconnectedEvent>((event) {
          if (mounted) {
            setState(() {
              _isReconnecting = false;
              _reconnectAttempt = 0;
            });
          }
        })
        ..on<RoomDisconnectedEvent>((event) {
          if (_phase != _CallPhase.ended && !_userEndedCall) {
            _onCallEnded();
          }
        })
        // Agent state monitoring: LiveKit AgentSession on the server publishes
        // "lk.agent.state" as a participant attribute. When it changes, we
        // update _agentState to drive UI changes:
        //   thinking → pulsing dots + "思考中..." + orange avatar glow
        //   speaking → waveform animation + "语音通话中"
        //   listening → default call UI
        ..on<ParticipantAttributesChanged>((event) {
          final state = event.attributes['lk.agent.state'];
          if (state != null && state != _agentState && mounted) {
            setState(() => _agentState = state);
          }
        })
        ..on<DataReceivedEvent>((event) {
          if (event.topic == 'text_reply') {
            try {
              final payload = jsonDecode(utf8.decode(event.data));
              final text = payload['text'] as String? ?? '';
              if (text.isNotEmpty && mounted) {
                setState(() {
                  _messages.add(_CallMessage(
                    text: text,
                    isUser: false,
                    timestamp: DateTime.now(),
                  ));
                });
                _scrollToBottom();
              }
            } catch (_) {}
          }
        });

      // 4. Connect to LiveKit room (with timeout)
      await _room!.connect(
        livekitUrl,
        token,
        connectOptions: const ConnectOptions(
          timeouts: Timeouts(
            connection: Duration(seconds: 15),
            debounce: Duration(milliseconds: 20),
            publish: Duration(seconds: 15),
            subscribe: Duration(seconds: 15),
            peerConnection: Duration(seconds: 15),
            iceRestart: Duration(seconds: 15),
          ),
        ),
        roomOptions: const RoomOptions(
          adaptiveStream: true,
          dynacast: true,
          defaultAudioPublishOptions: AudioPublishOptions(),
        ),
      );

      // 5. Enable microphone — muted by default in PTT mode
      await _room!.localParticipant?.setMicrophoneEnabled(!_isPttMode);

      // 6. Start duration timer
      _stopwatch.start();
      _durationTimer = Timer.periodic(const Duration(seconds: 1), (_) {
        if (!mounted) return;
        final elapsed = _stopwatch.elapsed;
        setState(() {
          _durationLabel =
              '${elapsed.inMinutes.toString().padLeft(2, '0')}:${(elapsed.inSeconds % 60).toString().padLeft(2, '0')}';
        });
      });

      _waveController.repeat(reverse: true);

      setState(() => _phase = _CallPhase.active);
    } catch (e) {
      if (mounted) {
        setState(() {
          _phase = _CallPhase.ringing;
          _errorMessage = '连接失败: ${_friendlyError(e)}';
        });
      }
    }
  }

  /// Start waveform animation driven by remote participant audio level.
  void _startWaveformUpdates() {
    _waveTimer?.cancel();
    _waveTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
      if (!mounted || _phase != _CallPhase.active) {
        timer.cancel();
        return;
      }

      // Get audio level from remote participant (the agent)
      double audioLevel = 0.0;
      final participants = _room?.remoteParticipants.values;
      if (participants != null && participants.isNotEmpty) {
        audioLevel = participants.first.audioLevel;
      }

      setState(() {
        final rand = Random();
        for (int i = 0; i < _waveHeights.length; i++) {
          _waveHeights[i] =
              (audioLevel * 2.0 + rand.nextDouble() * 0.2).clamp(0.1, 1.0);
        }
      });
    });
  }

  /// End the call and clean up all resources.
  Future<void> _endCall() async {
    _userEndedCall = true;
    setState(() => _phase = _CallPhase.ended);
    _stopwatch.stop();
    _durationTimer?.cancel();
    _waveController.stop();
    _waveTimer?.cancel();

    // Disconnect LiveKit room (handles all audio cleanup)
    try {
      await _room?.disconnect();
    } catch (_) {}
    try {
      await _room?.dispose();
    } catch (_) {}
    _listener?.dispose();

    // Navigate back
    if (mounted) {
      await Future.delayed(const Duration(milliseconds: 500));
      if (mounted) Navigator.of(context).pop();
    }
  }

  void _onCallEnded() {
    if (_phase == _CallPhase.ended) return;
    _endCall();
  }

  /// Toggle microphone mute.
  void _toggleMute() {
    _isMuted = !_isMuted;
    _room?.localParticipant?.setMicrophoneEnabled(!_isMuted);
    setState(() {});
  }

  /// Toggle speaker / earpiece.
  void _toggleSpeaker() {
    _isSpeaker = !_isSpeaker;
    Hardware.instance.setSpeakerphoneOn(_isSpeaker);
    setState(() {});
  }

  // ---------------------------------------------------------------------------
  // PTT (Push-to-Talk)
  // ---------------------------------------------------------------------------

  void _startPtt() {
    if (!_isPttMode || _phase != _CallPhase.active || _pttRecording) return;
    _room?.localParticipant?.setMicrophoneEnabled(true);
    _pttStopwatch
      ..reset()
      ..start();
    _pttTimer = Timer.periodic(const Duration(milliseconds: 50), (_) {
      if (mounted) setState(() {});
    });
    setState(() => _pttRecording = true);
  }

  void _stopPtt() {
    if (!_pttRecording) return;
    _room?.localParticipant?.setMicrophoneEnabled(false);
    _pttStopwatch.stop();
    _pttTimer?.cancel();
    _pttTimer = null;
    final elapsed = _pttStopwatch.elapsed;
    if (elapsed.inMilliseconds >= 300) {
      _messages.add(_CallMessage(
        text: '',
        isUser: true,
        timestamp: DateTime.now(),
        isVoice: true,
        voiceDurationMs: elapsed.inMilliseconds,
      ));
      _scrollToBottom();
    }
    setState(() => _pttRecording = false);
  }

  void _togglePttMode() {
    if (_pttRecording) _stopPtt();
    final newPtt = !_isPttMode;
    setState(() => _isPttMode = newPtt);
    if (_phase == _CallPhase.active) {
      _room?.localParticipant?.setMicrophoneEnabled(!newPtt);
    }
  }

  String _formatPttDuration() {
    final ms = _pttStopwatch.elapsedMilliseconds;
    final secs = ms ~/ 1000;
    final tenths = (ms % 1000) ~/ 100;
    return '$secs.$tenths"';
  }

  String _formatVoiceDuration(int ms) {
    final secs = ms ~/ 1000;
    if (secs < 60) return '${secs}s';
    final mins = secs ~/ 60;
    final s = secs % 60;
    return '$mins:${s.toString().padLeft(2, '0')}';
  }

  void _scrollToBottom() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_scrollController.hasClients) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 200),
          curve: Curves.easeOut,
        );
      }
    });
  }

  // ---------------------------------------------------------------------------
  // Text input + attachments (mixed-mode during voice call)
  // ---------------------------------------------------------------------------

  static const _maxAttachments = 5;

  Future<void> _sendTextMessage() async {
    final text = _textController.text.trim();
    if (text.isEmpty && _pendingAttachments.isEmpty) return;
    if (_room?.localParticipant == null) return;

    setState(() => _isSending = true);

    try {
      final payload = {
        'type': 'text_inject',
        'text': text,
        if (_pendingAttachments.isNotEmpty)
          'attachments': _pendingAttachments.map((a) => a.toJson()).toList(),
      };
      final jsonStr = jsonEncode(payload);
      final bytes = utf8.encode(jsonStr);

      // Check size limit (200KB for LiveKit data channel)
      if (bytes.length > 200 * 1024) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('消息太大，请减少附件数量或大小')),
          );
        }
        return;
      }

      await _room!.localParticipant!.publishData(
        bytes,
        reliable: true,
        topic: 'text_inject',
      );

      // Track message for display
      setState(() {
        _messages.add(_CallMessage(
          text: text,
          isUser: true,
          timestamp: DateTime.now(),
          attachments: _pendingAttachments.isNotEmpty
              ? List<ChatAttachment>.from(_pendingAttachments)
              : null,
        ));
        _pendingAttachments.clear();
      });
      _textController.clear();
      _scrollToBottom();
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('发送失败: $e')),
        );
      }
    } finally {
      if (mounted) setState(() => _isSending = false);
    }
  }

  void _showAttachmentOptions() {
    showModalBottomSheet(
      context: context,
      builder: (ctx) => SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              leading: const Icon(Icons.photo_library),
              title: const Text('从相册选择'),
              subtitle: const Text('支持多选'),
              onTap: () { Navigator.pop(ctx); _pickMultipleImages(); },
            ),
            ListTile(
              leading: const Icon(Icons.camera_alt),
              title: const Text('拍照'),
              onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); },
            ),
            ListTile(
              leading: const Icon(Icons.attach_file),
              title: const Text('选择文件'),
              subtitle: const Text('图片、PDF'),
              onTap: () { Navigator.pop(ctx); _pickFile(); },
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _pickImage(ImageSource source) async {
    if (_pendingAttachments.length >= _maxAttachments) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('最多添加 $_maxAttachments 个附件')),
        );
      }
      return;
    }

    final picker = ImagePicker();
    final picked = await picker.pickImage(
      source: source,
      maxWidth: 1024,
      maxHeight: 1024,
      imageQuality: 80,
    );
    if (picked == null) return;

    final bytes = await picked.readAsBytes();
    final ext = picked.path.split('.').last.toLowerCase();
    final mediaType = switch (ext) {
      'png' => 'image/png',
      'webp' => 'image/webp',
      'gif' => 'image/gif',
      _ => 'image/jpeg',
    };

    setState(() {
      _pendingAttachments.add(ChatAttachment(
        base64Data: base64Encode(bytes),
        mediaType: mediaType,
        fileName: picked.name,
      ));
    });
  }

  Future<void> _pickMultipleImages() async {
    final remaining = _maxAttachments - _pendingAttachments.length;
    if (remaining <= 0) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('最多添加 $_maxAttachments 个附件')),
        );
      }
      return;
    }

    final picker = ImagePicker();
    final pickedList = await picker.pickMultiImage(
      maxWidth: 1024,
      maxHeight: 1024,
      imageQuality: 80,
    );
    if (pickedList.isEmpty) return;

    final toAdd = pickedList.take(remaining);
    for (final picked in toAdd) {
      final bytes = await picked.readAsBytes();
      final ext = picked.path.split('.').last.toLowerCase();
      final mediaType = switch (ext) {
        'png' => 'image/png',
        'webp' => 'image/webp',
        'gif' => 'image/gif',
        _ => 'image/jpeg',
      };
      _pendingAttachments.add(ChatAttachment(
        base64Data: base64Encode(bytes),
        mediaType: mediaType,
        fileName: picked.name,
      ));
    }
    setState(() {});

    if (pickedList.length > remaining && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('已选择 $remaining 张，最多 $_maxAttachments 个')),
      );
    }
  }

  Future<void> _pickFile() async {
    final remaining = _maxAttachments - _pendingAttachments.length;
    if (remaining <= 0) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('最多添加 $_maxAttachments 个附件')),
        );
      }
      return;
    }

    final result = await FilePicker.platform.pickFiles(
      type: FileType.any,
      allowMultiple: true,
    );
    if (result == null || result.files.isEmpty) return;

    const allowedExts = {'jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf'};
    int skipped = 0;

    final toAdd = result.files.take(remaining);
    for (final file in toAdd) {
      if (file.path == null) continue;
      final ext = (file.extension ?? '').toLowerCase();
      if (!allowedExts.contains(ext)) {
        skipped++;
        continue;
      }
      final bytes = await File(file.path!).readAsBytes();
      final String mediaType;
      if (ext == 'pdf') {
        mediaType = 'application/pdf';
      } else {
        mediaType = switch (ext) {
          'png' => 'image/png',
          'webp' => 'image/webp',
          'gif' => 'image/gif',
          _ => 'image/jpeg',
        };
      }

      _pendingAttachments.add(ChatAttachment(
        base64Data: base64Encode(bytes),
        mediaType: mediaType,
        fileName: file.name,
      ));
    }
    setState(() {});

    if (skipped > 0 && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('仅支持图片(jpg/png/gif/webp)和PDF文件')),
      );
    } else if (result.files.length > remaining && mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('已选择 $remaining 个，最多 $_maxAttachments 个')),
      );
    }
  }

  // ---------------------------------------------------------------------------
  // Build
  // ---------------------------------------------------------------------------

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.background,
      body: SafeArea(
        child: _isInputExpanded && _phase == _CallPhase.active
            ? _buildChatModeLayout()
            : _buildCallModeLayout(),
      ),
    );
  }

  /// Full-screen call layout (avatar, waveform, big controls).
  Widget _buildCallModeLayout() {
    return Center(
      child: Column(
        children: [
          const Spacer(flex: 2),
          _buildAvatar(),
          const SizedBox(height: 24),
          Text(
            _statusText,
            textAlign: TextAlign.center,
            style: const TextStyle(
                fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          Text(
            _subtitleText,
            textAlign: TextAlign.center,
            style: const TextStyle(
                color: AppColors.textSecondary, fontSize: 15),
          ),
          if (_errorMessage != null) ...[
            const SizedBox(height: 16),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 32),
              child: Container(
                padding: const EdgeInsets.symmetric(
                    horizontal: 16, vertical: 12),
                decoration: BoxDecoration(
                  color: AppColors.error.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(
                      color: AppColors.error.withOpacity(0.3)),
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(Icons.error_outline,
                        size: 18, color: AppColors.error),
                    const SizedBox(width: 8),
                    Flexible(
                      child: Text(
                        _errorMessage!,
                        textAlign: TextAlign.center,
                        style: const TextStyle(
                          color: AppColors.error,
                          fontSize: 13,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
          const SizedBox(height: 32),
          if (_phase == _CallPhase.active)
            Text(
              _durationLabel,
              style: const TextStyle(
                fontSize: 40,
                fontWeight: FontWeight.w300,
                color: AppColors.textPrimary,
                letterSpacing: 4,
              ),
            ),
          const SizedBox(height: 24),
          if (_phase == _CallPhase.active && _isReconnecting)
            Padding(
              padding: const EdgeInsets.only(bottom: 12),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const SizedBox(
                    width: 14,
                    height: 14,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  ),
                  const SizedBox(width: 8),
                  Text(
                    '网络重连中${_reconnectAttempt > 0 ? ' ($_reconnectAttempt)' : ''}...',
                    style: TextStyle(
                      color: AppColors.warning,
                      fontSize: 13,
                    ),
                  ),
                ],
              ),
            ),
          if (_phase == _CallPhase.active)
            _agentState == 'thinking' ? _buildThinkingDots() : _buildWaveform(),
          if (_phase == _CallPhase.active && _isPttMode) ...[
            const SizedBox(height: 28),
            _buildPttButton(),
          ],
          const Spacer(flex: 3),
          _buildControls(),
          const SizedBox(height: 48),
        ],
      ),
    );
  }

  /// Chat-mode layout: compact header + message list + input bar.
  Widget _buildChatModeLayout() {
    return Column(
      children: [
        _buildCompactCallHeader(),
        const Divider(height: 1, color: AppColors.surfaceLight),
        Expanded(
          child: _messages.isEmpty
              ? Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.chat_bubble_outline,
                          size: 48, color: AppColors.textMuted.withOpacity(0.5)),
                      const SizedBox(height: 12),
                      const Text('输入文字或附件发送给 Agent',
                          style: TextStyle(color: AppColors.textSecondary, fontSize: 14)),
                      const SizedBox(height: 4),
                      const Text('Agent 将语音回复并显示在此',
                          style: TextStyle(color: AppColors.textMuted, fontSize: 12)),
                    ],
                  ),
                )
              : ListView.builder(
                  controller: _scrollController,
                  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                  itemCount: _messages.length,
                  itemBuilder: (ctx, i) => _buildMessageBubble(_messages[i]),
                ),
        ),
        if (_pendingAttachments.isNotEmpty)
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: _buildAttachmentPreview(),
          ),
        _buildTextInputArea(),
        const SizedBox(height: 4),
      ],
    );
  }

  /// Compact header for chat mode: green dot + title + duration + inline controls.
  Widget _buildCompactCallHeader() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      child: Row(
        children: [
          Container(
            width: 10,
            height: 10,
            margin: const EdgeInsets.only(left: 8),
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: _isReconnecting
                  ? AppColors.warning
                  : _agentState == 'thinking'
                      ? AppColors.warning
                      : AppColors.success,
            ),
          ),
          const SizedBox(width: 8),
          Text(AppLocalizations.of(context).appTitle,
              style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15)),
          const SizedBox(width: 8),
          Text(
              _agentState == 'thinking' ? AppLocalizations.of(context).agentCallThinking : _durationLabel,
              style: const TextStyle(color: AppColors.textSecondary, fontSize: 13)),
          if (_isReconnecting) ...[
            const SizedBox(width: 6),
            const SizedBox(
              width: 12, height: 12,
              child: CircularProgressIndicator(strokeWidth: 1.5),
            ),
          ],
          const Spacer(),
          IconButton(
            icon: Icon(_isMuted ? Icons.mic_off : Icons.mic, size: 20,
                color: _isMuted ? AppColors.error : AppColors.textSecondary),
            onPressed: _toggleMute,
            visualDensity: VisualDensity.compact,
            tooltip: _isMuted ? '取消静音' : '静音',
          ),
          IconButton(
            icon: const Icon(Icons.call_end, size: 20, color: AppColors.error),
            onPressed: _endCall,
            visualDensity: VisualDensity.compact,
            tooltip: '挂断',
          ),
          IconButton(
            icon: Icon(_isSpeaker ? Icons.volume_up : Icons.hearing, size: 20,
                color: AppColors.textSecondary),
            onPressed: _toggleSpeaker,
            visualDensity: VisualDensity.compact,
            tooltip: _isSpeaker ? '听筒' : '扬声器',
          ),
          IconButton(
            icon: const Icon(Icons.keyboard_hide, size: 20,
                color: AppColors.textSecondary),
            onPressed: () => setState(() => _isInputExpanded = false),
            visualDensity: VisualDensity.compact,
            tooltip: '收起',
          ),
        ],
      ),
    );
  }

  Widget _buildMessageBubble(_CallMessage msg) {
    if (msg.isVoice) return _buildVoiceMessageBubble(msg);
    final isUser = msg.isUser;
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        mainAxisAlignment:
            isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          if (!isUser) ...[
            Container(
              width: 28,
              height: 28,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: AppColors.primary.withOpacity(0.15),
              ),
              child: const Icon(Icons.smart_toy, size: 16, color: AppColors.primary),
            ),
            const SizedBox(width: 8),
          ],
          Flexible(
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
              decoration: BoxDecoration(
                color: isUser
                    ? AppColors.primary.withOpacity(0.15)
                    : AppColors.surfaceLight,
                borderRadius: BorderRadius.only(
                  topLeft: const Radius.circular(16),
                  topRight: const Radius.circular(16),
                  bottomLeft: Radius.circular(isUser ? 16 : 4),
                  bottomRight: Radius.circular(isUser ? 4 : 16),
                ),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  if (msg.attachments != null && msg.attachments!.isNotEmpty) ...[
                    Wrap(
                      spacing: 4,
                      runSpacing: 4,
                      children: msg.attachments!.map((att) {
                        final isImage = att.mediaType.startsWith('image/');
                        if (isImage) {
                          return ClipRRect(
                            borderRadius: BorderRadius.circular(8),
                            child: Image.memory(
                              base64Decode(att.base64Data),
                              width: 100,
                              height: 100,
                              fit: BoxFit.cover,
                              cacheWidth: 200,
                              cacheHeight: 200,
                            ),
                          );
                        }
                        return Container(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 8, vertical: 4),
                          decoration: BoxDecoration(
                            color: AppColors.surface,
                            borderRadius: BorderRadius.circular(6),
                          ),
                          child: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              const Icon(Icons.description,
                                  size: 16, color: AppColors.textSecondary),
                              const SizedBox(width: 4),
                              Text(att.fileName ?? 'file',
                                  style: const TextStyle(fontSize: 12)),
                            ],
                          ),
                        );
                      }).toList(),
                    ),
                    if (msg.text.isNotEmpty) const SizedBox(height: 6),
                  ],
                  if (msg.text.isNotEmpty)
                    Text(
                      msg.text,
                      style: const TextStyle(
                        color: AppColors.textPrimary,
                        fontSize: 14,
                      ),
                    ),
                ],
              ),
            ),
          ),
          if (isUser) const SizedBox(width: 8),
        ],
      ),
    );
  }

  String get _statusText {
    switch (_phase) {
      case _CallPhase.ringing:
        return AppLocalizations.of(context).agentCallRingingStatus;
      case _CallPhase.connecting:
        return AppLocalizations.of(context).agentCallConnectingStatus;
      case _CallPhase.active:
        return AppLocalizations.of(context).agentCallActiveStatus;
      case _CallPhase.ended:
        return AppLocalizations.of(context).agentCallEndedStatus;
    }
  }

  String get _subtitleText {
    if (_errorMessage != null && _phase == _CallPhase.ringing) {
      return '';
    }
    switch (_phase) {
      case _CallPhase.ringing:
        return '智能运维语音助手';
      case _CallPhase.connecting:
        return '正在建立安全连接';
      case _CallPhase.active:
        if (_agentState == 'thinking') return AppLocalizations.of(context).agentCallThinking;
        if (_agentState == 'initializing') return '正在初始化...';
        return '语音通话中';
      case _CallPhase.ended:
        return _durationLabel;
    }
  }

  /// Extract a short user-friendly message from an exception.
  String _friendlyError(dynamic e) {
    final s = e.toString();
    final match = RegExp(r'status code of (\d+)').firstMatch(s);
    if (match != null) {
      final code = match.group(1);
      switch (code) {
        case '503':
          return '语音服务暂不可用 ($code)';
        case '502':
          return '语音服务网关错误 ($code)';
        case '404':
          return '语音服务未找到 ($code)';
        default:
          return '服务器错误 ($code)';
      }
    }
    if (s.contains('SocketException') || s.contains('Connection refused')) {
      return '无法连接到服务器';
    }
    if (s.contains('TimeoutException') || s.contains('timed out')) {
      return '连接超时，请检查网络';
    }
    if (s.length > 80) return '${s.substring(0, 80)}...';
    return s;
  }

  Widget _buildAvatar() {
    final isActive = _phase == _CallPhase.active;
    final isThinking = isActive && _agentState == 'thinking';

    final avatar = AnimatedContainer(
      duration: const Duration(milliseconds: 400),
      width: isActive ? 100 : 120,
      height: isActive ? 100 : 120,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: AppColors.primary.withOpacity(0.15),
        border: Border.all(
          color: isThinking
              ? AppColors.warning
              : isActive
                  ? AppColors.success
                  : AppColors.primary,
          width: 3,
        ),
        boxShadow: isActive
            ? [
                BoxShadow(
                  color: (isThinking ? AppColors.warning : AppColors.success)
                      .withOpacity(0.3),
                  blurRadius: 24,
                  spreadRadius: 4,
                ),
              ]
            : [],
      ),
      child: Icon(
        Icons.smart_toy,
        size: isActive ? 48 : 56,
        color: isThinking
            ? AppColors.warning
            : isActive
                ? AppColors.success
                : AppColors.primary,
      ),
    );

    if (!isThinking) return avatar;

    // Pulsing glow ring around avatar when thinking
    return AnimatedBuilder(
      animation: _thinkingController,
      builder: (context, child) {
        final pulse = (sin(_thinkingController.value * 2 * pi) + 1) / 2;
        return Container(
          width: 120,
          height: 120,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            boxShadow: [
              BoxShadow(
                color: AppColors.warning.withOpacity(0.15 + pulse * 0.25),
                blurRadius: 20 + pulse * 16,
                spreadRadius: pulse * 8,
              ),
            ],
          ),
          child: child,
        );
      },
      child: avatar,
    );
  }

  Widget _buildWaveform() {
    return SizedBox(
      height: 60,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: List.generate(_waveHeights.length, (i) {
          return AnimatedContainer(
            duration: const Duration(milliseconds: 120),
            width: 4,
            height: 10 + _waveHeights[i] * 50,
            margin: const EdgeInsets.symmetric(horizontal: 2),
            decoration: BoxDecoration(
              color: AppColors.primary
                  .withOpacity(0.6 + _waveHeights[i] * 0.4),
              borderRadius: BorderRadius.circular(2),
            ),
          );
        }),
      ),
    );
  }

  /// Three bouncing dots animation shown while agent is thinking.
  Widget _buildThinkingDots() {
    return SizedBox(
      height: 60,
      child: AnimatedBuilder(
        animation: _thinkingController,
        builder: (context, _) {
          return Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(3, (i) {
              final offset = i * 0.33;
              final t = (_thinkingController.value + offset) % 1.0;
              final bounce = sin(t * pi);
              return Container(
                width: 12,
                height: 12,
                margin: const EdgeInsets.symmetric(horizontal: 6),
                transform: Matrix4.translationValues(0, -bounce * 16, 0),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: AppColors.warning.withOpacity(0.5 + bounce * 0.5),
                ),
              );
            }),
          );
        },
      ),
    );
  }

  Widget _buildTextInputArea() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (_pendingAttachments.isNotEmpty)
            Padding(
              padding: const EdgeInsets.only(bottom: 8),
              child: _buildAttachmentPreview(),
            ),
          Container(
            decoration: BoxDecoration(
              color: AppColors.surface.withOpacity(0.92),
              borderRadius: BorderRadius.circular(24),
              border: Border.all(color: AppColors.surfaceLight.withOpacity(0.6)),
            ),
            child: Row(
              children: [
                Padding(
                  padding: const EdgeInsets.only(left: 4),
                  child: IconButton(
                    icon: const Icon(Icons.add_circle_outline, size: 22),
                    onPressed: _showAttachmentOptions,
                  ),
                ),
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: const InputDecoration(
                      hintText: '输入文字或添加附件...',
                      hintStyle: TextStyle(color: AppColors.textMuted),
                      border: InputBorder.none,
                      contentPadding: EdgeInsets.symmetric(vertical: 12),
                    ),
                    textInputAction: TextInputAction.send,
                    onSubmitted: (_) => _sendTextMessage(),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(right: 4),
                  child: IconButton(
                    icon: _isSending
                        ? const SizedBox(
                            width: 20, height: 20,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Icon(Icons.send, size: 20),
                    onPressed: _isSending ? null : _sendTextMessage,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildAttachmentPreview() {
    return SizedBox(
      height: 80,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: _pendingAttachments.length,
        itemBuilder: (ctx, i) {
          final att = _pendingAttachments[i];
          final isImage = att.mediaType.startsWith('image/');
          return Stack(
            children: [
              Padding(
                padding: const EdgeInsets.all(4),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: isImage
                      ? Image.memory(
                          base64Decode(att.base64Data),
                          width: 72, height: 72,
                          fit: BoxFit.cover,
                          cacheWidth: 144, cacheHeight: 144,
                        )
                      : Container(
                          width: 72, height: 72,
                          color: AppColors.surfaceLight,
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              const Icon(Icons.description, size: 28, color: AppColors.textSecondary),
                              const SizedBox(height: 2),
                              Text(
                                att.fileName?.split('.').last.toUpperCase() ?? 'FILE',
                                style: const TextStyle(fontSize: 10, color: AppColors.textMuted),
                                overflow: TextOverflow.ellipsis,
                              ),
                            ],
                          ),
                        ),
                ),
              ),
              Positioned(
                top: 0,
                right: 0,
                child: GestureDetector(
                  onTap: () => setState(() => _pendingAttachments.removeAt(i)),
                  child: Container(
                    decoration: const BoxDecoration(
                      color: Colors.black54,
                      shape: BoxShape.circle,
                    ),
                    child: const Icon(Icons.close, size: 16, color: Colors.white),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  /// Large press-and-hold PTT button shown in call mode.
  Widget _buildPttButton() {
    return Listener(
      onPointerDown: (_) => _startPtt(),
      onPointerUp: (_) => _stopPtt(),
      onPointerCancel: (_) => _stopPtt(),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 120),
        width: _pttRecording ? 100 : 84,
        height: _pttRecording ? 100 : 84,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: _pttRecording
              ? AppColors.error.withOpacity(0.15)
              : AppColors.primary.withOpacity(0.12),
          border: Border.all(
            color: _pttRecording ? AppColors.error : AppColors.primary,
            width: _pttRecording ? 3 : 2,
          ),
          boxShadow: _pttRecording
              ? [
                  BoxShadow(
                    color: AppColors.error.withOpacity(0.35),
                    blurRadius: 24,
                    spreadRadius: 4,
                  )
                ]
              : [],
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              _pttRecording ? Icons.mic : Icons.mic_none,
              size: _pttRecording ? 36 : 30,
              color: _pttRecording ? AppColors.error : AppColors.primary,
            ),
            const SizedBox(height: 2),
            Text(
              _pttRecording ? _formatPttDuration() : '按住说话',
              style: TextStyle(
                fontSize: _pttRecording ? 12 : 11,
                color: _pttRecording ? AppColors.error : AppColors.textSecondary,
                fontWeight: _pttRecording ? FontWeight.w600 : FontWeight.normal,
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// Voice message bubble: decorative waveform + duration.
  Widget _buildVoiceMessageBubble(_CallMessage msg) {
    final rand = Random(msg.voiceDurationMs ^ msg.timestamp.millisecondsSinceEpoch);
    final bars = List.generate(18, (_) => 0.15 + rand.nextDouble() * 0.85);
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
            decoration: BoxDecoration(
              color: AppColors.primary.withOpacity(0.15),
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(16),
                topRight: Radius.circular(16),
                bottomLeft: Radius.circular(16),
                bottomRight: Radius.circular(4),
              ),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                const Icon(Icons.mic, size: 16, color: AppColors.primary),
                const SizedBox(width: 6),
                Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: bars
                      .map((h) => Container(
                            width: 3,
                            height: 6 + h * 18,
                            margin: const EdgeInsets.symmetric(horizontal: 1),
                            decoration: BoxDecoration(
                              color: AppColors.primary.withOpacity(0.65),
                              borderRadius: BorderRadius.circular(1.5),
                            ),
                          ))
                      .toList(),
                ),
                const SizedBox(width: 6),
                Text(
                  _formatVoiceDuration(msg.voiceDurationMs),
                  style: const TextStyle(
                    color: AppColors.textSecondary,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(width: 8),
        ],
      ),
    );
  }

  Widget _buildControls() {
    switch (_phase) {
      case _CallPhase.ringing:
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            FloatingActionButton(
              heroTag: 'decline',
              backgroundColor: AppColors.error,
              onPressed: () => Navigator.of(context).pop(),
              child: const Icon(Icons.call_end, color: Colors.white),
            ),
            const SizedBox(width: 40),
            FloatingActionButton(
              heroTag: 'accept',
              backgroundColor: AppColors.success,
              onPressed: _acceptCall,
              child: const Icon(Icons.call, color: Colors.white),
            ),
          ],
        );

      case _CallPhase.connecting:
        return const CircularProgressIndicator();

      case _CallPhase.active:
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _CircleButton(
              icon: _isPttMode ? Icons.touch_app : Icons.wifi_tethering,
              label: _isPttMode ? 'PTT' : '自由',
              isActive: _isPttMode,
              onTap: _togglePttMode,
            ),
            const SizedBox(width: 16),
            _CircleButton(
              icon: _isInputExpanded ? Icons.keyboard_hide : Icons.keyboard,
              label: _isInputExpanded ? '收起' : '键盘',
              isActive: _isInputExpanded,
              onTap: () => setState(() => _isInputExpanded = !_isInputExpanded),
            ),
            const SizedBox(width: 16),
            if (!_isPttMode) ...[
              _CircleButton(
                icon: _isMuted ? Icons.mic_off : Icons.mic,
                label: _isMuted ? '取消静音' : '静音',
                isActive: _isMuted,
                onTap: _toggleMute,
              ),
              const SizedBox(width: 16),
            ],
            FloatingActionButton(
              heroTag: 'end',
              backgroundColor: AppColors.error,
              onPressed: _endCall,
              child: const Icon(Icons.call_end, color: Colors.white),
            ),
            const SizedBox(width: 16),
            _CircleButton(
              icon: _isSpeaker ? Icons.volume_up : Icons.hearing,
              label: _isSpeaker ? '扬声器' : '听筒',
              isActive: _isSpeaker,
              onTap: _toggleSpeaker,
            ),
          ],
        );

      case _CallPhase.ended:
        return const Icon(Icons.call_end,
            size: 48, color: AppColors.textMuted);
    }
  }

  @override
  void dispose() {
    _userEndedCall = true;
    _textController.dispose();
    _scrollController.dispose();
    _durationTimer?.cancel();
    _waveTimer?.cancel();
    _pttTimer?.cancel();
    _pttStopwatch.stop();
    _waveController.dispose();
    _thinkingController.dispose();
    _stopwatch.stop();
    _room?.disconnect();
    _room?.dispose();
    _listener?.dispose();
    super.dispose();
  }
}

// ---------------------------------------------------------------------------
// Simple message model for call-page chat display
// ---------------------------------------------------------------------------

class _CallMessage {
  final String text;
  final bool isUser;
  final DateTime timestamp;
  final List<ChatAttachment>? attachments;
  final bool isVoice;
  final int voiceDurationMs;

  _CallMessage({
    required this.text,
    required this.isUser,
    required this.timestamp,
    this.attachments,
    this.isVoice = false,
    this.voiceDurationMs = 0,
  });
}

// ---------------------------------------------------------------------------
// Small helper widget for mute / speaker buttons
// ---------------------------------------------------------------------------

class _CircleButton extends StatelessWidget {
  final IconData icon;
  final String label;
  final bool isActive;
  final VoidCallback onTap;

  const _CircleButton({
    required this.icon,
    required this.label,
    required this.isActive,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        GestureDetector(
          onTap: onTap,
          child: Container(
            width: 52,
            height: 52,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: isActive
                  ? AppColors.textPrimary.withOpacity(0.2)
                  : AppColors.surfaceLight,
            ),
            child: Icon(icon, color: AppColors.textPrimary),
          ),
        ),
        const SizedBox(height: 6),
        Text(
          label,
          style:
              const TextStyle(color: AppColors.textSecondary, fontSize: 11),
        ),
      ],
    );
  }
}
