import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:it0_app/l10n/app_localizations.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/robot_avatar.dart';
import '../../../../core/widgets/floating_robot_fab.dart';
import '../../domain/entities/chat_message.dart';
import '../providers/chat_providers.dart';
import '../widgets/timeline_event_node.dart';
import '../widgets/stream_text_widget.dart';
import '../widgets/approval_action_card.dart';
import '../widgets/conversation_drawer.dart';
import '../../../agent_call/presentation/pages/agent_call_page.dart';
import '../widgets/voice_mic_button.dart';

// ---------------------------------------------------------------------------
// Chat page – Timeline workflow style (inspired by Claude Code VSCode)
// ---------------------------------------------------------------------------

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

  @override
  ConsumerState<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends ConsumerState<ChatPage> {
  final _messageController = TextEditingController();
  final _scrollController = ScrollController();
  final List<ChatAttachment> _pendingAttachments = [];
  bool _sttLoading = false;

  // -- Send ------------------------------------------------------------------

  void _send() {
    final text = _messageController.text.trim();
    if (text.isEmpty && _pendingAttachments.isEmpty) return;
    _messageController.clear();
    final attachments = _pendingAttachments.isNotEmpty
        ? List<ChatAttachment>.from(_pendingAttachments)
        : null;
    if (_pendingAttachments.isNotEmpty) {
      setState(() => _pendingAttachments.clear());
    }
    ref.read(chatProvider.notifier).sendMessage(text, attachments: attachments);
    _scrollToBottom();
  }

  void _inject() {
    final text = _messageController.text.trim();
    if (text.isEmpty) return;
    _messageController.clear();
    ref.read(chatProvider.notifier).injectMessage(text);
    _scrollToBottom();
  }

  Future<void> _transcribeToInput(String audioPath) async {
    setState(() {
      _sttLoading = true;
      _messageController.text = AppLocalizations.of(context).chatRecognizingLabel;
    });
    try {
      final text = await ref.read(chatProvider.notifier).transcribeAudio(audioPath);
      if (mounted) {
        setState(() {
          _messageController.text = text;
          _messageController.selection = TextSelection.collapsed(
            offset: text.length,
          );
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() => _messageController.text = '');
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(AppLocalizations.of(context).chatSpeechRecognitionError)),
        );
      }
    } finally {
      if (mounted) setState(() => _sttLoading = false);
    }
  }

  void _scrollToBottom({bool jump = false}) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!_scrollController.hasClients) return;
      final target = _scrollController.position.maxScrollExtent + 80;
      if (jump) {
        _scrollController.jumpTo(target);
        // Second frame: layout may still be settling for large message lists
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (_scrollController.hasClients) {
            _scrollController.jumpTo(
              _scrollController.position.maxScrollExtent + 80,
            );
          }
        });
      } else {
        _scrollController.animateTo(
          target,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
        );
      }
    });
  }

  void _openVoiceCall() {
    Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => const AgentCallPage()),
    );
  }

  // -- Attachments -----------------------------------------------------------

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

  static const _maxAttachments = 5;

  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: 1568,
      maxHeight: 1568,
      imageQuality: 85,
    );
    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: 1568,
      maxHeight: 1568,
      imageQuality: 85,
    );
    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;
    }

    // Use FileType.any so the system picker shows subdirectories for navigation.
    // We validate file extensions after selection.
    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 个')),
      );
    }
  }

  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),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  /// Whether to show a virtual "working" node at the bottom of the timeline.
  /// True when the agent is streaming but no assistant message has appeared yet.
  bool _needsWorkingNode(ChatState chatState) {
    if (!chatState.isStreaming) return false;
    if (chatState.messages.isEmpty) return false;
    // Show working node if the last message is still the user's prompt
    return chatState.messages.last.role == MessageRole.user;
  }

  // -- Timeline node builder ------------------------------------------------

  Widget _buildTimelineNode(
    ChatMessage message,
    ChatState chatState, {
    required bool isFirst,
    required bool isLast,
  }) {
    final isLastMessage = chatState.messages.last.id == message.id;
    final isStreamingNow = isLastMessage && chatState.isStreaming;

    // User message — special "prompt" node
    if (message.role == MessageRole.user) {
      return TimelineEventNode(
        status: NodeStatus.completed,
        label: message.content,
        isFirst: isFirst,
        isLast: isLast,
        icon: Icons.person_outline,
        content: message.attachments != null && message.attachments!.isNotEmpty
            ? Wrap(
                spacing: 4,
                runSpacing: 4,
                children: message.attachments!.map((att) {
                  final bytes = base64Decode(att.base64Data);
                  return ClipRRect(
                    borderRadius: BorderRadius.circular(8),
                    child: Image.memory(bytes, width: 120, height: 120, fit: BoxFit.cover, cacheWidth: 240, cacheHeight: 240),
                  );
                }).toList(),
              )
            : null,
      );
    }

    switch (message.type) {
      case MessageType.thinking:
        return TimelineEventNode(
          status: isStreamingNow ? NodeStatus.active : NodeStatus.completed,
          label: AppLocalizations.of(context).chatThinkingLabel,
          isFirst: isFirst,
          isLast: isLast,
          content: _CollapsibleThinking(
            text: message.content,
            isStreaming: isStreamingNow,
          ),
        );

      case MessageType.toolUse:
        final tool = message.toolExecution;
        final toolName = tool?.toolName ?? 'unknown';
        final isExecuting = tool?.status == ToolStatus.executing;
        return TimelineEventNode(
          status: isExecuting ? NodeStatus.active : NodeStatus.completed,
          label: toolName,
          isFirst: isFirst,
          isLast: isLast,
          icon: isExecuting ? null : Icons.check_circle_outline,
          content: tool != null && tool.input.isNotEmpty
              ? CodeBlock(text: tool.input, maxLines: 1)
              : null,
        );

      case MessageType.toolResult:
        final tool = message.toolExecution;
        final isError = tool?.status == ToolStatus.error;
        return TimelineEventNode(
          status: isError ? NodeStatus.error : NodeStatus.completed,
          label: isError ? AppLocalizations.of(context).chatExecutionFailedLabel : AppLocalizations.of(context).chatExecutionResultLabel,
          isFirst: isFirst,
          isLast: isLast,
          icon: isError ? Icons.cancel_outlined : Icons.check_circle_outline,
          content: tool?.output != null && tool!.output!.isNotEmpty
              ? _CollapsibleCodeBlock(
                  text: tool.output!,
                  textColor: isError ? AppColors.error : null,
                )
              : null,
        );

      case MessageType.approval:
        return TimelineEventNode(
          status: NodeStatus.warning,
          label: AppLocalizations.of(context).chatNeedsApprovalLabel,
          isFirst: isFirst,
          isLast: isLast,
          icon: Icons.shield_outlined,
          content: message.approvalRequest != null
              ? ApprovalActionCard(
                  approvalRequest: message.approvalRequest!,
                  onApprove: () => ref.read(chatProvider.notifier)
                      .approveCommand(message.approvalRequest!.taskId),
                  onReject: (reason) => ref.read(chatProvider.notifier)
                      .rejectCommand(message.approvalRequest!.taskId, reason: reason),
                )
              : null,
        );

      case MessageType.standingOrderDraft:
        return TimelineEventNode(
          status: NodeStatus.warning,
          label: AppLocalizations.of(context).chatStandingOrderDraftLabel,
          isFirst: isFirst,
          isLast: isLast,
          icon: Icons.schedule,
          content: _StandingOrderContent(
            draft: message.metadata ?? {},
            onConfirm: () => ref.read(chatProvider.notifier)
                .confirmStandingOrder(message.metadata ?? {}),
          ),
        );

      case MessageType.interrupted:
        return TimelineEventNode(
          status: NodeStatus.warning,
          label: message.content,
          isFirst: isFirst,
          isLast: isLast,
          icon: Icons.cancel_outlined,
        );

      case MessageType.text:
      default:
        return TimelineEventNode(
          status: isStreamingNow ? NodeStatus.active : NodeStatus.completed,
          label: isStreamingNow ? AppLocalizations.of(context).chatReplyingLabel : AppLocalizations.of(context).chatReplyLabel,
          isFirst: isFirst,
          isLast: isLast,
          icon: isStreamingNow ? null : Icons.check_circle_outline,
          content: StreamTextWidget(
            text: message.content,
            isStreaming: isStreamingNow,
            style: const TextStyle(
              color: AppColors.textPrimary,
              fontSize: 14,
            ),
          ),
        );
    }
  }

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

  @override
  Widget build(BuildContext context) {
    final chatState = ref.watch(chatProvider);

    // Auto-scroll when messages change
    ref.listen(chatProvider, (prev, next) {
      // Jump (no animation) when loading a conversation history
      final wasEmpty = prev?.messages.isEmpty ?? true;
      final nowHasMany = next.messages.length > 1;
      _scrollToBottom(jump: wasEmpty && nowHasMany);
    });

    return Scaffold(
      drawer: const ConversationDrawer(),
      appBar: AppBar(
        title: Consumer(
          builder: (context, ref, _) {
            final robotState = ref.watch(robotStateProvider);
            return Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                RobotAvatar(state: robotState, size: 32),
                const SizedBox(width: 8),
                Text(AppLocalizations.of(context).appTitle,
                    style: const TextStyle(
                        fontSize: 16, fontWeight: FontWeight.w600)),
              ],
            );
          },
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.edit_outlined, size: 20),
            tooltip: AppLocalizations.of(context).chatNewConversationTooltip,
            visualDensity: VisualDensity.compact,
            onPressed: () => ref.read(chatProvider.notifier).startNewChat(),
          ),
          if (chatState.isStreaming)
            IconButton(
              icon: const Icon(Icons.stop_circle_outlined, size: 20),
              tooltip: AppLocalizations.of(context).chatStopTooltip,
              visualDensity: VisualDensity.compact,
              onPressed: () => ref.read(chatProvider.notifier).cancelCurrentTask(),
            ),
          IconButton(
            icon: const Icon(Icons.call, size: 20),
            tooltip: AppLocalizations.of(context).chatVoiceCallTooltip,
            visualDensity: VisualDensity.compact,
            onPressed: _openVoiceCall,
          ),
          const SizedBox(width: 4),
        ],
      ),
      body: SafeArea(
        top: false,
        child: Column(
        children: [
          // Error banner
          if (chatState.error != null)
            Container(
              width: double.infinity,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
              color: AppColors.error.withOpacity(0.1),
              child: Row(
                children: [
                  const Icon(Icons.error_outline, size: 16, color: AppColors.error),
                  const SizedBox(width: 8),
                  Expanded(
                    child: Text(
                      chatState.error!,
                      style: const TextStyle(color: AppColors.error, fontSize: 13),
                    ),
                  ),
                  GestureDetector(
                    onTap: () => ref.read(chatProvider.notifier).clearChat(),
                    child: const Icon(Icons.close, size: 16, color: AppColors.error),
                  ),
                ],
              ),
            ),

          // Timeline message list + floating input
          Expanded(
            child: Stack(
              children: [
                // Messages fill the entire area
                chatState.messages.isEmpty
                    ? _buildEmptyState()
                    : ListView.builder(
                        controller: _scrollController,
                        // Bottom padding leaves room for the floating input pill
                        padding: const EdgeInsets.fromLTRB(12, 8, 12, 80),
                        itemCount: chatState.messages.length +
                            (_needsWorkingNode(chatState) ? 1 : 0),
                        itemBuilder: (context, index) {
                          if (index == chatState.messages.length &&
                              _needsWorkingNode(chatState)) {
                            return TimelineEventNode(
                              status: NodeStatus.active,
                              label: AppLocalizations.of(context).chatProcessingLabel,
                              isFirst: false,
                              isLast: false,
                            );
                          }
                          final isRealLast =
                              index == chatState.messages.length - 1;
                          return _buildTimelineNode(
                            chatState.messages[index],
                            chatState,
                            isFirst: index == 0,
                            isLast: isRealLast &&
                                !chatState.isStreaming &&
                                !_needsWorkingNode(chatState),
                          );
                        },
                      ),
                // Floating input pill at bottom
                Positioned(
                  left: 12,
                  right: 12,
                  bottom: 8,
                  child: _buildInputArea(chatState),
                ),
              ],
            ),
          ),
        ],
      ),
      ),
    );
  }

  Widget _buildEmptyState() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.smart_toy_outlined, size: 64, color: AppColors.textMuted),
          const SizedBox(height: 16),
          Text(
            AppLocalizations.of(context).chatStartConversationPrompt,
            style: const TextStyle(color: AppColors.textSecondary, fontSize: 16),
          ),
          const SizedBox(height: 8),
          Text(
            AppLocalizations.of(context).chatInputInstructionHint,
            style: const TextStyle(color: AppColors.textMuted, fontSize: 13),
          ),
          const SizedBox(height: 24),
          OutlinedButton.icon(
            onPressed: _openVoiceCall,
            icon: const Icon(Icons.call),
            label: const Text('语音通话'),
            style: OutlinedButton.styleFrom(
              foregroundColor: AppColors.success,
              side: const BorderSide(color: AppColors.success),
              padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildInputArea(ChatState chatState) {
    final isAwaitingApproval = chatState.agentStatus == AgentStatus.awaitingApproval;
    final isStreaming = chatState.isStreaming && !isAwaitingApproval;

    return Container(
      decoration: BoxDecoration(
        color: AppColors.surface.withOpacity(0.92),
        borderRadius: BorderRadius.circular(28),
        border: Border.all(color: AppColors.surfaceLight.withOpacity(0.6)),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.25),
            blurRadius: 12,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (_pendingAttachments.isNotEmpty)
            Padding(
              padding: const EdgeInsets.fromLTRB(12, 8, 12, 0),
              child: _buildAttachmentPreview(),
            ),
          Row(
            children: [
              if (!isStreaming)
                Padding(
                  padding: const EdgeInsets.only(left: 4),
                  child: IconButton(
                    icon: const Icon(Icons.add_circle_outline, size: 22),
                    tooltip: AppLocalizations.of(context).chatAddImageTooltip,
                    onPressed: isAwaitingApproval ? null : _showAttachmentOptions,
                  ),
                ),
              Expanded(
                child: TextField(
                  controller: _messageController,
                  decoration: InputDecoration(
                    hintText: isStreaming ? AppLocalizations.of(context).chatAdditionalInstructionHint : AppLocalizations.of(context).chatInstructionHint,
                    hintStyle: TextStyle(color: AppColors.textMuted),
                    border: InputBorder.none,
                    contentPadding: EdgeInsets.only(
                      left: isStreaming ? 16 : 4,
                      right: 4,
                      top: 12,
                      bottom: 12,
                    ),
                  ),
                  textInputAction: TextInputAction.send,
                  onSubmitted: (_) => isStreaming ? _inject() : _send(),
                  enabled: !isAwaitingApproval,
                ),
              ),
              if (isStreaming)
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                      icon: const Icon(Icons.send, color: AppColors.info, size: 20),
                      tooltip: AppLocalizations.of(context).chatInjectionTooltip,
                      onPressed: _inject,
                    ),
                    Padding(
                      padding: const EdgeInsets.only(right: 4),
                      child: IconButton(
                        icon: const Icon(Icons.stop_circle_outlined, color: AppColors.error, size: 20),
                        tooltip: AppLocalizations.of(context).chatStopTooltip,
                        onPressed: () => ref.read(chatProvider.notifier).cancelCurrentTask(),
                      ),
                    ),
                  ],
                )
              else
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    VoiceMicButton(
                      disabled: isAwaitingApproval || _sttLoading,
                      onAudioReady: _transcribeToInput,
                    ),
                    Padding(
                      padding: const EdgeInsets.only(right: 4),
                      child: IconButton(
                        icon: const Icon(Icons.send, size: 20),
                        onPressed: isAwaitingApproval ? null : _send,
                      ),
                    ),
                  ],
                ),
            ],
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _messageController.dispose();
    _scrollController.dispose();
    super.dispose();
  }
}

// ---------------------------------------------------------------------------
// Standing order content (embedded in timeline node)
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Collapsible code block for tool results – collapsed by default, tap to expand
// ---------------------------------------------------------------------------

class _CollapsibleCodeBlock extends StatefulWidget {
  final String text;
  final Color? textColor;

  const _CollapsibleCodeBlock({
    required this.text,
    this.textColor,
  });

  @override
  State<_CollapsibleCodeBlock> createState() => _CollapsibleCodeBlockState();
}

class _CollapsibleCodeBlockState extends State<_CollapsibleCodeBlock> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    final lineCount = '\n'.allMatches(widget.text).length + 1;
    // Short results (≤3 lines): always show fully
    if (lineCount <= 3) {
      return CodeBlock(text: widget.text, textColor: widget.textColor);
    }

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        ClipRect(
          child: AnimatedCrossFade(
            firstChild: CodeBlock(text: widget.text, textColor: widget.textColor),
            secondChild: CodeBlock(
              text: widget.text,
              textColor: widget.textColor,
              maxLines: 3,
            ),
            crossFadeState: _expanded
                ? CrossFadeState.showFirst
                : CrossFadeState.showSecond,
            duration: const Duration(milliseconds: 200),
            sizeCurve: Curves.easeInOut,
          ),
        ),
        GestureDetector(
          onTap: () => setState(() => _expanded = !_expanded),
          child: Padding(
            padding: const EdgeInsets.only(top: 4),
            child: Text(
              _expanded ? AppLocalizations.of(context).chatCollapseLabel : '展开 ($lineCount 行)',
              style: TextStyle(
                color: AppColors.info,
                fontSize: 12,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ),
      ],
    );
  }
}

// ---------------------------------------------------------------------------
// Collapsible thinking content – expanded while streaming, collapsed when done
// ---------------------------------------------------------------------------

class _CollapsibleThinking extends StatefulWidget {
  final String text;
  final bool isStreaming;

  const _CollapsibleThinking({
    required this.text,
    required this.isStreaming,
  });

  @override
  State<_CollapsibleThinking> createState() => _CollapsibleThinkingState();
}

class _CollapsibleThinkingState extends State<_CollapsibleThinking> {
  bool _expanded = true;

  @override
  void didUpdateWidget(covariant _CollapsibleThinking oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Auto-collapse when streaming ends
    if (oldWidget.isStreaming && !widget.isStreaming) {
      setState(() => _expanded = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    // While streaming, always show expanded content
    if (widget.isStreaming) {
      return StreamTextWidget(
        text: widget.text,
        isStreaming: true,
        style: const TextStyle(
          color: AppColors.textSecondary,
          fontSize: 13,
          fontStyle: FontStyle.italic,
        ),
      );
    }

    // Completed – show collapsible toggle
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        GestureDetector(
          onTap: () => setState(() => _expanded = !_expanded),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                'Thinking',
                style: TextStyle(
                  color: AppColors.textMuted,
                  fontSize: 12,
                  fontWeight: FontWeight.w500,
                ),
              ),
              const SizedBox(width: 4),
              AnimatedRotation(
                turns: _expanded ? 0.5 : 0.0,
                duration: const Duration(milliseconds: 200),
                child: Icon(
                  Icons.expand_more,
                  size: 16,
                  color: AppColors.textMuted,
                ),
              ),
            ],
          ),
        ),
        ClipRect(
          child: AnimatedCrossFade(
            firstChild: Padding(
              padding: const EdgeInsets.only(top: 6),
              child: StreamTextWidget(
                text: widget.text,
                isStreaming: false,
                style: const TextStyle(
                  color: AppColors.textSecondary,
                  fontSize: 13,
                  fontStyle: FontStyle.italic,
                ),
              ),
            ),
            secondChild: const SizedBox.shrink(),
            crossFadeState: _expanded
                ? CrossFadeState.showFirst
                : CrossFadeState.showSecond,
            duration: const Duration(milliseconds: 200),
            sizeCurve: Curves.easeInOut,
          ),
        ),
      ],
    );
  }
}

// ---------------------------------------------------------------------------
// Standing order content (embedded in timeline node)
// ---------------------------------------------------------------------------

class _StandingOrderContent extends StatelessWidget {
  final Map<String, dynamic> draft;
  final VoidCallback onConfirm;

  const _StandingOrderContent({
    required this.draft,
    required this.onConfirm,
  });

  @override
  Widget build(BuildContext context) {
    final name = draft['name'] as String? ?? draft['orderName'] as String? ?? '未命名指令';
    final cron = draft['cron'] as String? ?? draft['schedule'] as String? ?? '';
    final command = draft['command'] as String? ?? draft['prompt'] as String? ?? '';
    final targets = draft['targets'] as List? ?? draft['servers'] as List? ?? [];

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(name, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
        if (cron.isNotEmpty) ...[
          const SizedBox(height: 4),
          Text(
            cron,
            style: const TextStyle(
              color: AppColors.textSecondary,
              fontSize: 12,
              fontFamily: 'monospace',
            ),
          ),
        ],
        if (command.isNotEmpty) ...[
          const SizedBox(height: 6),
          CodeBlock(text: command),
        ],
        if (targets.isNotEmpty) ...[
          const SizedBox(height: 4),
          Text(
            '目标: ${targets.join(", ")}',
            style: const TextStyle(color: AppColors.textSecondary, fontSize: 12),
          ),
        ],
        const SizedBox(height: 8),
        Row(
          children: [
            OutlinedButton(
              onPressed: () {},
              style: OutlinedButton.styleFrom(
                foregroundColor: AppColors.textSecondary,
                padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
                minimumSize: Size.zero,
              ),
              child: Text(AppLocalizations.of(context).cancelButton, style: const TextStyle(fontSize: 12)),
            ),
            const SizedBox(width: 8),
            FilledButton(
              onPressed: onConfirm,
              style: FilledButton.styleFrom(
                backgroundColor: AppColors.primary,
                padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
                minimumSize: Size.zero,
              ),
              child: const Text('确认', style: TextStyle(fontSize: 12)),
            ),
          ],
        ),
      ],
    );
  }
}
