import 'package:flutter/material.dart';
import 'dart:io' show File;
import 'dart:convert' show base64Decode;
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io' as io;
import 'package:crypto/crypto.dart' as crypto;
import 'package:intl/intl.dart';
import 'package:gwid/models/message.dart';
import 'package:gwid/models/contact.dart';
import 'package:gwid/theme_provider.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
import 'dart:ui';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:gwid/chat_screen.dart';
import 'package:gwid/services/avatar_cache_service.dart';
import 'package:gwid/api/api_service.dart';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:open_file/open_file.dart';
import 'package:gwid/full_screen_video_player.dart';
import 'package:just_audio/just_audio.dart';
import 'package:gwid/services/cache_service.dart';
import 'package:video_player/video_player.dart';

bool _currentIsDark = false;

enum MessageReadStatus {
  sending, // Отправляется (часы)
  sent, // Отправлено (1 галочка)
  read, // Прочитано (2 галочки)
}

// Service для отслеживания прогресса загрузки файлов
class FileDownloadProgressService {
  static final FileDownloadProgressService _instance =
      FileDownloadProgressService._internal();
  factory FileDownloadProgressService() => _instance;
  FileDownloadProgressService._internal();

  final Map<String, ValueNotifier<double>> _progressNotifiers = {};
  bool _initialized = false;

  // Initialize on first access to load saved download status
  Future<void> _ensureInitialized() async {
    if (_initialized) return;

    try {
      final prefs = await SharedPreferences.getInstance();

      // Load fileId -> filePath mappings
      final fileIdMap = prefs.getStringList('file_id_to_path_map') ?? [];

      // Mark all downloaded files as completed (progress = 1.0)
      for (final mapping in fileIdMap) {
        final parts = mapping.split(':');
        if (parts.length >= 2) {
          final fileId = parts[0];
          final filePath = parts.skip(1).join(':'); // In case path contains ':'

          final file = io.File(filePath);
          if (await file.exists()) {
            if (!_progressNotifiers.containsKey(fileId)) {
              _progressNotifiers[fileId] = ValueNotifier<double>(1.0);
            } else {
              _progressNotifiers[fileId]!.value = 1.0;
            }
          }
        }
      }

      _initialized = true;
    } catch (e) {
      print('Error initializing download status: $e');
      _initialized = true; // Mark as initialized to avoid retrying indefinitely
    }
  }

  ValueNotifier<double> getProgress(String fileId) {
    _ensureInitialized(); // Ensure initialization
    if (!_progressNotifiers.containsKey(fileId)) {
      _progressNotifiers[fileId] = ValueNotifier<double>(-1);
    }
    return _progressNotifiers[fileId]!;
  }

  void updateProgress(String fileId, double progress) {
    if (!_progressNotifiers.containsKey(fileId)) {
      _progressNotifiers[fileId] = ValueNotifier<double>(progress);
    } else {
      _progressNotifiers[fileId]!.value = progress;
    }
  }

  void clearProgress(String fileId) {
    _progressNotifiers.remove(fileId);
  }
}

Color _getUserColor(int userId, BuildContext context) {
  final bool isDark = Theme.of(context).brightness == Brightness.dark;

  if (isDark != _currentIsDark) {
    _currentIsDark = isDark;
  }

  final List<Color> materialYouColors = isDark
      ? [
          // Темная тема
          const Color(0xFFEF5350), // Красный
          const Color(0xFFEC407A), // Розовый
          const Color(0xFFAB47BC), // Фиолетовый
          const Color(0xFF7E57C2), // Глубокий фиолетовый
          const Color(0xFF5C6BC0), // Индиго
          const Color(0xFF42A5F5), // Синий
          const Color(0xFF29B6F6), // Голубой
          const Color(0xFF26C6DA), // Бирюзовый
          const Color(0xFF26A69A), // Теal
          const Color(0xFF66BB6A), // Зеленый
          const Color(0xFF9CCC65), // Светло-зеленый
          const Color(0xFFD4E157), // Лаймовый
          const Color(0xFFFFEB3B), // Желтый
          const Color(0xFFFFCA28), // Янтарный
          const Color(0xFFFFA726), // Оранжевый
          const Color(0xFFFF7043), // Глубокий оранжевый
          const Color(0xFF8D6E63), // Коричневый
          const Color(0xFF78909C), // Сине-серый
          const Color(0xFFB39DDB), // Лавандовый
          const Color(0xFF80CBC4), // Аквамариновый
          const Color(0xFFC5E1A5), // Светло-зеленый пастельный
        ]
      : [
          // Светлая тема
          const Color(0xFFF44336), // Красный
          const Color(0xFFE91E63), // Розовый
          const Color(0xFF9C27B0), // Фиолетовый
          const Color(0xFF673AB7), // Глубокий фиолетовый
          const Color(0xFF3F51B5), // Индиго
          const Color(0xFF2196F3), // Синий
          const Color(0xFF03A9F4), // Голубой
          const Color(0xFF00BCD4), // Бирюзовый
          const Color(0xFF009688), // Теal
          const Color(0xFF4CAF50), // Зеленый
          const Color(0xFF8BC34A), // Светло-зеленый
          const Color(0xFFCDDC39), // Лаймовый
          const Color(0xFFFFEE58), // Желтый
          const Color(0xFFFFC107), // Янтарный
          const Color(0xFFFF9800), // Оранжевый
          const Color(0xFFFF5722), // Глубокий оранжевый
          const Color(0xFF795548), // Коричневый
          const Color(0xFF607D8B), // Сине-серый
          const Color(0xFF9575CD), // Лавандовый
          const Color(0xFF4DB6AC), // Бирюзовый светлый
          const Color(0xFFAED581), // Зеленый пастельный
        ];

  final colorIndex = userId % materialYouColors.length;
  final color = materialYouColors[colorIndex];

  return color;
}

class ChatMessageBubble extends StatelessWidget {
  final Message message;
  final bool isMe;
  final MessageReadStatus? readStatus;
  final bool deferImageLoading;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  final VoidCallback? onDeleteForMe;
  final VoidCallback? onDeleteForAll;
  final Function(String)? onReaction;
  final VoidCallback? onRemoveReaction;
  final VoidCallback? onReply;
  final VoidCallback? onForward;
  final int? myUserId;
  final bool? canEditMessage;
  final bool isGroupChat;
  final bool isChannel;
  final String? senderName;
  final String? forwardedFrom;
  final String? forwardedFromAvatarUrl;
  final Map<int, Contact>? contactDetailsCache;
  final Function(String)? onReplyTap;
  final bool useAutoReplyColor;
  final Color? customReplyColor;
  final bool isFirstInGroup;
  final bool isLastInGroup;
  final bool isGrouped;
  final double avatarVerticalOffset;
  final int? chatId;

  const ChatMessageBubble({
    super.key,
    required this.message,
    required this.isMe,
    this.readStatus,
    this.deferImageLoading = false,
    this.onEdit,
    this.onDelete,
    this.onDeleteForMe,
    this.onDeleteForAll,
    this.onReaction,
    this.onRemoveReaction,
    this.onReply,
    this.onForward,
    this.myUserId,
    this.canEditMessage,
    this.isGroupChat = false,
    this.isChannel = false,
    this.senderName,
    this.forwardedFrom,
    this.forwardedFromAvatarUrl,
    this.contactDetailsCache,
    this.onReplyTap,
    this.useAutoReplyColor = true,
    this.customReplyColor,
    this.isFirstInGroup = false,
    this.isLastInGroup = false,
    this.isGrouped = false,
    this.avatarVerticalOffset =
        -35.0, // выше ниже аватарку бля как хотите я жрать хочу
    this.chatId,
  });

  String _formatMessageTime(BuildContext context, int timestamp) {
    final dt = DateTime.fromMillisecondsSinceEpoch(timestamp);
    final showSeconds = Provider.of<ThemeProvider>(
      context,
      listen: false,
    ).showSeconds;
    return DateFormat(showSeconds ? 'HH:mm:ss' : 'HH:mm').format(dt);
  }

  EdgeInsets _getMessageMargin(BuildContext context) {
    if (isLastInGroup) {
      return const EdgeInsets.only(bottom: 12);
    }
    if (isFirstInGroup) {
      return const EdgeInsets.only(bottom: 3);
    }
    return const EdgeInsets.only(bottom: 3);
  }

  Widget _buildForwardedMessage(
    BuildContext context,
    Map<String, dynamic> link,
    Color textColor,
    double messageTextOpacity,
    bool isUltraOptimized,
  ) {
    final forwardedMessage = link['message'] as Map<String, dynamic>?;
    if (forwardedMessage == null) return const SizedBox.shrink();

    final text = forwardedMessage['text'] as String? ?? '';
    final attaches =
        (forwardedMessage['attaches'] as List?)
            ?.map((e) => (e as Map).cast<String, dynamic>())
            .toList() ??
        [];

    String forwardedSenderName;
    String? forwardedSenderAvatarUrl = forwardedFromAvatarUrl;

    if (forwardedFrom != null) {
      forwardedSenderName = forwardedFrom!;
    } else {
      final chatName = link['chatName'] as String?;
      final chatIconUrl = link['chatIconUrl'] as String?;

      if (chatName != null) {
        forwardedSenderName = chatName;
        forwardedSenderAvatarUrl ??= chatIconUrl;
      } else {
        final originalSenderId = forwardedMessage['sender'] as int?;
        final cache = contactDetailsCache;
        if (originalSenderId != null && cache != null) {
          final originalSenderContact = cache[originalSenderId];
          forwardedSenderName =
              originalSenderContact?.name ?? 'Участник $originalSenderId';
          forwardedSenderAvatarUrl ??= originalSenderContact?.photoBaseUrl;
        } else {
          forwardedSenderName = 'Неизвестный';
        }
      }
    }

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
      decoration: BoxDecoration(
        color: textColor.withOpacity(0.08 * messageTextOpacity),
        border: Border(
          left: BorderSide(
            color: textColor.withOpacity(0.3 * messageTextOpacity),
            width: 3, // Делаем рамку жирнее для отличия от ответа
          ),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // "Заголовок" с именем автора и аватаркой
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                Icons.forward,
                size: 14,
                color: textColor.withOpacity(0.6 * messageTextOpacity),
              ),
              const SizedBox(width: 6),
              if (forwardedSenderAvatarUrl != null)
                Container(
                  width: 20,
                  height: 20,
                  margin: const EdgeInsets.only(right: 6),
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    border: Border.all(
                      color: textColor.withOpacity(0.2 * messageTextOpacity),
                      width: 1,
                    ),
                  ),
                  child: ClipOval(
                    child: Image.network(
                      forwardedSenderAvatarUrl,
                      fit: BoxFit.cover,
                      errorBuilder: (context, error, stackTrace) {
                        return Container(
                          color: textColor.withOpacity(
                            0.1 * messageTextOpacity,
                          ),
                          child: Icon(
                            Icons.person,
                            size: 12,
                            color: textColor.withOpacity(
                              0.5 * messageTextOpacity,
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                )
              else
                Container(
                  width: 20,
                  height: 20,
                  margin: const EdgeInsets.only(right: 6),
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: textColor.withOpacity(0.1 * messageTextOpacity),
                    border: Border.all(
                      color: textColor.withOpacity(0.2 * messageTextOpacity),
                      width: 1,
                    ),
                  ),
                  child: Icon(
                    Icons.person,
                    size: 12,
                    color: textColor.withOpacity(0.5 * messageTextOpacity),
                  ),
                ),
              Flexible(
                child: Text(
                  forwardedSenderName,
                  style: TextStyle(
                    fontSize: 13,
                    fontWeight: FontWeight.bold,
                    color: textColor.withOpacity(0.9 * messageTextOpacity),
                  ),
                  overflow: TextOverflow.ellipsis,
                ),
              ),
            ],
          ),
          const SizedBox(height: 6),

          // Содержимое пересланного сообщения (фото и/или текст)
          if (attaches.isNotEmpty) ...[
            ..._buildPhotosWithCaption(
              context,
              attaches, // Передаем вложения из вложенного сообщения
              textColor,
              isUltraOptimized,
              messageTextOpacity,
            ),
            const SizedBox(height: 6),
          ],
          if (text.isNotEmpty)
            Text(
              text,
              style: TextStyle(
                color: textColor.withOpacity(0.9 * messageTextOpacity),
                fontSize: 14,
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildVideoCirclePlayer({
    required BuildContext context,
    required int videoId,
    required String messageId,
    String? highQualityUrl,
    Uint8List? lowQualityBytes,
  }) {
    return _VideoCirclePlayer(
      videoId: videoId,
      messageId: messageId,
      chatId: chatId!,
      highQualityUrl: highQualityUrl,
      lowQualityBytes: lowQualityBytes,
    );
  }

  Widget _buildVideoPreview({
    required BuildContext context,
    required int videoId,
    required String messageId,
    String? highQualityUrl,
    Uint8List? lowQualityBytes,
    int? videoType,
  }) {
    // Логика открытия плеера
    void openFullScreenVideo() async {
      // Показываем индикатор загрузки, пока получаем URL
      showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) => const Center(child: CircularProgressIndicator()),
      );

      try {
        final videoUrl = await ApiService.instance.getVideoUrl(
          videoId,
          chatId!, // chatId из `build`
          messageId,
        );

        if (!context.mounted) return; // [!code ++] Проверка правильным способом
        Navigator.pop(context); // Убираем индикатор
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => FullScreenVideoPlayer(videoUrl: videoUrl),
          ),
        );
      } catch (e) {
        if (!context.mounted) return; // [!code ++] Проверка правильным способом
        Navigator.pop(context); // Убираем индикатор
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Не удалось загрузить видео: $e'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }

    final isVideoCircle = videoType == 1;

    if (isVideoCircle) {
      return _buildVideoCirclePlayer(
        context: context,
        videoId: videoId,
        messageId: messageId,
        highQualityUrl: highQualityUrl,
        lowQualityBytes: lowQualityBytes,
      );
    }

    return GestureDetector(
      onTap: openFullScreenVideo,
      child: AspectRatio(
        aspectRatio: 16 / 9,
        child: ClipRRect(
          borderRadius: BorderRadius.circular(12),
          child: Stack(
            alignment: Alignment.center,
            fit: StackFit.expand,
            children: [
              // Если у нас есть ХОТЬ ЧТО-ТО (блюр или URL), показываем ProgressiveImage
              (highQualityUrl != null && highQualityUrl.isNotEmpty) ||
                      (lowQualityBytes != null)
                  ? _ProgressiveNetworkImage(
                      url: highQualityUrl ?? '',
                      previewBytes: lowQualityBytes,
                      width: 220,
                      height: 160,
                      fit: BoxFit.cover,
                      keepAlive: false,
                    )
                  : Container(
                      color: Colors.black26,
                      child: const Center(
                        child: Icon(
                          Icons.video_library_outlined,
                          color: Colors.white,
                          size: 40,
                        ),
                      ),
                    ),
              Container(
                decoration: BoxDecoration(
                  color: Colors.black.withOpacity(0.15),
                ),
                child: Icon(
                  Icons.play_circle_filled_outlined,
                  color: Colors.white.withOpacity(0.95),
                  size: 50,
                  shadows: const [
                    Shadow(
                      color: Colors.black38,
                      blurRadius: 4,
                      offset: Offset(0, 2),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildReplyPreview(
    BuildContext context,
    Map<String, dynamic> link,
    Color textColor,
    double messageTextOpacity,
    bool isUltraOptimized,
    double messageBorderRadius,
  ) {
    final replyMessage = link['message'] as Map<String, dynamic>?;
    if (replyMessage == null) return const SizedBox.shrink();

    final replyText = replyMessage['text'] as String? ?? '';
    final replySenderId = replyMessage['sender'] as int?;
    final replyMessageId = replyMessage['id'] as String?;

    final isDarkMode = Theme.of(context).brightness == Brightness.dark;

    Color replyAccentColor;
    if (useAutoReplyColor) {
      replyAccentColor = _getUserColor(replySenderId ?? 0, context);
    } else {
      replyAccentColor =
          customReplyColor ??
          (isDarkMode ? const Color(0xFF90CAF9) : const Color(0xFF1976D2));
    }

    // Вычисляем оптимальную ширину на основе длины текста
    final textLength = replyText.length;
    final minWidth = 120.0; // Минимальная ширина для коротких сообщений

    // Адаптивная ширина: минимум 120px, растет в зависимости от длины текста
    double adaptiveWidth = minWidth;
    if (textLength > 0) {
      // Базовый расчет: примерно 8px на символ + отступы
      adaptiveWidth = (textLength * 8.0 + 32).clamp(minWidth, double.infinity);
    }

    return GestureDetector(
      onTap: () {
        // Вызываем callback для прокрутки к оригинальному сообщению
        if (replyMessageId != null && onReplyTap != null) {
          onReplyTap!(replyMessageId);
        }
      },
      child: Container(
        constraints: BoxConstraints(minWidth: minWidth, minHeight: 40),
        width: adaptiveWidth, // Используем адаптивную ширину
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
        decoration: BoxDecoration(
          color: isDarkMode
              ? replyAccentColor.withOpacity(
                  0.15,
                ) // Полупрозрачный фон для темной темы
              : replyAccentColor.withOpacity(
                  0.08,
                ), // Более прозрачный для светлой
          borderRadius: BorderRadius.circular(
            (isUltraOptimized ? 4 : messageBorderRadius) * 0.3,
          ),
          border: Border(
            left: BorderSide(
              color: replyAccentColor, // Цвет левой границы
              width: 2,
            ),
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisSize: MainAxisSize.min,
          children: [
            // Ник автора сообщения
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.reply, size: 12, color: replyAccentColor),
                const SizedBox(width: 4),
                Expanded(
                  child: Text(
                    replySenderId != null
                        ? (contactDetailsCache?[replySenderId]?.name ??
                              'Участник $replySenderId')
                        : 'Неизвестный',
                    style: TextStyle(
                      fontSize: 10,
                      fontWeight: FontWeight.w600,
                      color: replyAccentColor,
                    ),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 2),
            // Текст сообщения
            Align(
              alignment: Alignment.centerLeft,
              child: Text(
                replyText.isNotEmpty ? replyText : 'Фото',
                style: const TextStyle(fontSize: 11, color: Colors.white),
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                softWrap: true,
                textAlign: TextAlign.left,
              ),
            ),
          ],
        ),
      ),
    );
  }
  /*
  void _showMessageContextMenu(BuildContext context) {
    // Список реакций, отсортированный по популярности
    const reactions = [
      '👍',
      '❤️',
      '😂',
      '🔥',
      '👏',
      '👌',
      '🎉',
      '🥰',
      '😍',
      '🙏',
      '🤔',
      '🤯',
      '💯',
      '⚡️',
      '🤟',
      '🌚',
      '🌝',
      '🥱',
      '🤣',
      '🫠',
      '🫡',
      '🐱',
      '💋',
      '😘',
      '🐶',
      '🤝',
      '⭐️',
      '🍷',
      '🍑',
      '😁',
      '🤷‍♀️',
      '🤷‍♂️',
      '👩‍❤️‍👨',
      '🦄',
      '👻',
      '🗿',
      '❤️‍🩹',
      '🛑',
      '⛄️',
      '❓',
      '🙄',
      '❗️',
      '😉',
      '😳',
      '🥳',
      '😎',
      '💪',
      '👀',
      '🤞',
      '🤤',
      '🤪',
      '🤩',
      '😴',
      '😐',
      '😇',
      '🖤',
      '👑',
      '👋',
      '👁️',
    ];

    // Проверяем, есть ли уже реакция от пользователя
    final hasUserReaction =
        message.reactionInfo != null &&
        message.reactionInfo!['yourReaction'] != null;

    showModalBottomSheet(
      context: context,
      backgroundColor: Colors
          .transparent, // Фон делаем прозрачным, чтобы скругление было видно
      builder: (context) => Container(
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.surface,
          borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
        ),
        child: SafeArea(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              // Реакции
              if (onReaction != null) ...[
                // Контейнер для прокручиваемого списка эмодзи
                SizedBox(
                  height: 80, // Задаем высоту для ряда с реакциями
                  child: SingleChildScrollView(
                    scrollDirection: Axis.horizontal,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 16.0,
                      vertical: 12.0,
                    ),
                    child: Row(
                      children: [
                        ...reactions.map(
                          (emoji) => Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 4.0,
                            ),
                            child: GestureDetector(
                              onTap: () {
                                Navigator.pop(context);
                                onReaction!(emoji);
                              },
                              child: Text(
                                emoji,
                                style: const TextStyle(fontSize: 32),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                // Кнопка удаления реакции, если есть реакция от пользователя
                if (hasUserReaction && onRemoveReaction != null) ...[
                  Padding(
                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
                    child: SizedBox(
                      width: double.infinity,
                      child: ElevatedButton.icon(
                        onPressed: () {
                          Navigator.pop(context);
                          onRemoveReaction!();
                        },
                        icon: const Icon(Icons.remove_circle_outline),
                        label: const Text('Убрать реакцию'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Theme.of(
                            context,
                          ).colorScheme.errorContainer,
                          foregroundColor: Theme.of(
                            context,
                          ).colorScheme.onErrorContainer,
                        ),
                      ),
                    ),
                  ),
                ],
                const Divider(height: 1),
              ],
              // Действия с сообщением (остаются без изменений)
              if (onReply != null && !isChannel)
                ListTile(
                  leading: const Icon(Icons.reply),
                  title: const Text('Ответить'),
                  onTap: () {
                    Navigator.pop(context);
                    onReply!();
                  },
                ),
              if (onEdit != null)
                ListTile(
                  leading: Icon(
                    canEditMessage == false ? Icons.edit_off : Icons.edit,
                    color: canEditMessage == false ? Colors.grey : null,
                  ),
                  title: Text(
                    canEditMessage == false
                        ? 'Редактировать (недоступно)'
                        : 'Редактировать',
                    style: TextStyle(
                      color: canEditMessage == false ? Colors.grey : null,
                    ),
                  ),
                  onTap: canEditMessage == false
                      ? null
                      : () {
                          Navigator.pop(context);
                          onEdit!();
                        },
                ),
              if (onDeleteForMe != null ||
                  onDeleteForAll != null ||
                  onDelete != null) ...[
                if (onEdit != null) const Divider(height: 1),
                if (onDeleteForMe != null)
                  ListTile(
                    leading: const Icon(
                      Icons.person_remove,
                      color: Colors.redAccent,
                    ),
                    title: const Text(
                      'Удалить у меня',
                      style: TextStyle(color: Colors.redAccent),
                    ),
                    onTap: () {
                      Navigator.pop(context);
                      onDeleteForMe?.call();
                    },
                  ),
                if (onDeleteForAll != null)
                  ListTile(
                    leading: const Icon(
                      Icons.delete_forever,
                      color: Colors.red,
                    ),
                    title: const Text(
                      'Удалить у всех',
                      style: TextStyle(color: Colors.red),
                    ),
                    onTap: () {
                      Navigator.pop(context);
                      onDeleteForAll?.call();
                    },
                  ),
                if (onDelete != null &&
                    onDeleteForMe == null &&
                    onDeleteForAll == null)
                  ListTile(
                    leading: const Icon(Icons.delete, color: Colors.red),
                    title: const Text(
                      'Удалить',
                      style: TextStyle(color: Colors.red),
                    ),
                    onTap: () {
                      Navigator.pop(context);
                      onDelete!.call();
                    },
                  ),
              ],
            ],
          ),
        ),
      ),
    );
  }
  */

  void _showMessageContextMenu(BuildContext context, Offset tapPosition) {
    final hasUserReaction = message.reactionInfo?['yourReaction'] != null;

    showDialog(
      context: context,
      barrierColor: Colors.transparent, // Делаем фон прозрачным
      builder: (context) {
        return _MessageContextMenu(
          message: message,
          position: tapPosition,
          onReply: onReply,
          onEdit: onEdit,
          onDeleteForMe: onDeleteForMe,
          onDeleteForAll: onDeleteForAll,
          onReaction: onReaction,
          onRemoveReaction: onRemoveReaction,
          onForward: onForward,
          canEditMessage: canEditMessage ?? false,
          hasUserReaction: hasUserReaction,
          isChannel: isChannel,
        );
      },
    );
  }

  Widget _buildReactionsWidget(BuildContext context, Color textColor) {
    if (message.reactionInfo == null ||
        message.reactionInfo!['counters'] == null) {
      return const SizedBox.shrink();
    }

    final counters = message.reactionInfo!['counters'] as List<dynamic>;
    if (counters.isEmpty) {
      return const SizedBox.shrink();
    }

    return Padding(
      padding: const EdgeInsets.only(top: 8.0),
      child: Wrap(
        spacing: 4.0,
        runSpacing: 4.0,
        alignment: isMe ? WrapAlignment.end : WrapAlignment.start,
        children: counters.map<Widget>((counter) {
          final emoji = counter['reaction'] as String;
          final count = counter['count'] as int;
          final isUserReaction = message.reactionInfo!['yourReaction'] == emoji;

          return GestureDetector(
            onTap: () {
              if (isUserReaction) {
                // Если это наша реакция - удаляем
                onRemoveReaction?.call();
              } else {
                // Если это чужая реакция - добавляем такую же
                onReaction?.call(emoji);
              }
            },
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: isUserReaction
                    ? Theme.of(context).colorScheme.primary.withOpacity(0.3)
                    : textColor.withOpacity(0.1),
                borderRadius: BorderRadius.circular(16),
              ),
              child: Text(
                '$emoji $count',
                style: TextStyle(
                  fontSize: 12,
                  fontWeight: isUserReaction
                      ? FontWeight.w600
                      : FontWeight.w500,
                  color: isUserReaction
                      ? Theme.of(context).colorScheme.primary
                      : textColor.withOpacity(0.9),
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    final isUltraOptimized = themeProvider.ultraOptimizeChats;

    final isStickerOnly =
        message.attaches.length == 1 &&
        message.attaches.any((a) => a['_type'] == 'STICKER') &&
        message.text.isEmpty;
    if (isStickerOnly) {
      return _buildStickerOnlyMessage(context);
    }

    final isVideoCircle =
        message.attaches.length == 1 &&
        message.attaches.any(
          (a) => a['_type'] == 'VIDEO' && (a['videoType'] as int?) == 1,
        ) &&
        message.text.isEmpty;
    if (isVideoCircle) {
      return _buildVideoCircleOnlyMessage(context);
    }

    final hasUnsupportedContent = _hasUnsupportedMessageTypes();

    final messageOpacity = themeProvider.messageBubbleOpacity;
    final messageTextOpacity = themeProvider.messageTextOpacity;
    final messageShadowIntensity = themeProvider.messageShadowIntensity;
    final messageBorderRadius = themeProvider.messageBorderRadius;

    final bubbleColor = _getBubbleColor(isMe, themeProvider, messageOpacity);
    final textColor = _getTextColor(
      isMe,
      bubbleColor,
      messageTextOpacity,
      context,
    );
    final bubbleDecoration = _createBubbleDecoration(
      bubbleColor,
      messageBorderRadius,
      messageShadowIntensity,
    );

    if (hasUnsupportedContent) {
      return _buildUnsupportedMessage(
        context,
        bubbleColor,
        textColor,
        bubbleDecoration,
      );
    }

    final baseTextStyle =
        Theme.of(context).textTheme.bodyMedium ?? const TextStyle();
    final defaultTextStyle = baseTextStyle.copyWith(color: textColor);
    final linkColor = _getLinkColor(bubbleColor, isMe);
    final linkStyle = baseTextStyle.copyWith(
      color: linkColor,
      decoration: TextDecoration.underline,
      decorationColor: linkColor,
    );

    Future<void> _onOpenLink(LinkableElement link) async {
      final uri = Uri.parse(link.url);
      if (await canLaunchUrl(uri)) {
        await launchUrl(uri, mode: LaunchMode.externalApplication);
      } else {
        if (context.mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Не удалось открыть ссылку: ${link.url}')),
          );
        }
      }
    }

    void _onSenderNameTap() {
      openUserProfileById(context, message.senderId);
    }

    final messageContentChildren = _buildMessageContentChildren(
      context,
      textColor,
      messageTextOpacity,
      isUltraOptimized,
      linkStyle,
      defaultTextStyle,
      messageBorderRadius,
      _onOpenLink,
      _onSenderNameTap,
    );

    Widget messageContent = _buildMessageContentInner(
      context,
      bubbleDecoration,
      messageContentChildren,
    );

    if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
      messageContent = GestureDetector(
        onTapDown: (TapDownDetails details) {
          _showMessageContextMenu(context, details.globalPosition);
        },
        child: messageContent,
      );
    }

    return Column(
      crossAxisAlignment: isMe
          ? CrossAxisAlignment.end
          : CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: isMe
              ? MainAxisAlignment.end
              : MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            if (!isMe && isGroupChat && !isChannel) ...[
              SizedBox(
                width: 40,
                child: isLastInGroup
                    ? Transform.translate(
                        offset: Offset(0, avatarVerticalOffset),
                        child: _buildSenderAvatar(),
                      )
                    : null,
              ),
            ],
            Flexible(child: messageContent),
          ],
        ),
      ],
    );
  }

  List<Widget> _buildInlineKeyboard(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
  ) {
    // 1. Ищем вложение с клавиатурой
    final keyboardAttach = attaches.firstWhere(
      (a) => a['_type'] == 'INLINE_KEYBOARD',
      orElse: () =>
          <String, dynamic>{}, // Возвращаем пустую карту, если не найдено
    );

    if (keyboardAttach.isEmpty) {
      return []; // Нет клавиатуры
    }

    // 2. Парсим структуру кнопок
    final keyboardData = keyboardAttach['keyboard'] as Map<String, dynamic>?;
    final buttonRows = keyboardData?['buttons'] as List<dynamic>?;

    if (buttonRows == null || buttonRows.isEmpty) {
      return []; // Нет кнопок
    }

    final List<Widget> rows = [];

    // 3. Создаем виджеты для каждого ряда кнопок
    for (final row in buttonRows) {
      if (row is List<dynamic> && row.isNotEmpty) {
        final List<Widget> buttonsInRow = [];

        // 4. Создаем виджеты для каждой кнопки в ряду
        for (final buttonData in row) {
          if (buttonData is Map<String, dynamic>) {
            final String? text = buttonData['text'] as String?;
            final String? type = buttonData['type'] as String?;
            final String? url = buttonData['url'] as String?;

            // Нас интересуют только кнопки-ссылки (как в вашем JSON)
            if (text != null && type == 'LINK' && url != null) {
              buttonsInRow.add(
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 2.0),
                    child: FilledButton(
                      onPressed: () =>
                          _launchURL(context, url), // Открываем ссылку
                      style: FilledButton.styleFrom(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 12,
                        ),
                        // Стилизуем под цвет сообщения
                        backgroundColor: textColor.withOpacity(0.1),
                        foregroundColor: textColor.withOpacity(0.9),
                      ),
                      child: Text(
                        text,
                        textAlign: TextAlign.center,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ),
                ),
              );
            }
          }
        }

        // Добавляем готовый ряд кнопок
        if (buttonsInRow.isNotEmpty) {
          rows.add(
            Padding(
              padding: const EdgeInsets.only(top: 4.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: buttonsInRow,
              ),
            ),
          );
        }
      }
    }

    // Возвращаем Column с рядами кнопок
    if (rows.isNotEmpty) {
      return [
        Padding(
          padding: const EdgeInsets.only(top: 8.0),
          child: Column(children: rows),
        ),
      ];
    }

    return [];
  }

  // Helper-метод для открытия ссылок
  Future<void> _launchURL(BuildContext context, String url) async {
    final uri = Uri.parse(url);
    if (await canLaunchUrl(uri)) {
      await launchUrl(uri, mode: LaunchMode.externalApplication);
    } else {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Не удалось открыть ссылку: $url'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  bool _hasUnsupportedMessageTypes() {
    final hasUnsupportedAttachments = message.attaches.any((attach) {
      final type = attach['_type']?.toString().toUpperCase();
      return type == 'VOICE' ||
          type == 'GIF' ||
          type == 'LOCATION' ||
          type == 'CONTACT';
    });

    return hasUnsupportedAttachments;
  }

  Widget _buildUnsupportedMessage(
    BuildContext context,
    Color bubbleColor,
    Color textColor,
    BoxDecoration bubbleDecoration,
  ) {
    return Column(
      crossAxisAlignment: isMe
          ? CrossAxisAlignment.end
          : CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: isMe
              ? MainAxisAlignment.end
              : MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            if (!isMe && isGroupChat && !isChannel) ...[
              //шлем в пезду аватарку если это я, анал.
              SizedBox(
                width: 40,
                child:
                    isLastInGroup //Если это соо в группе, и оно последнее в группе соо
                    ? Transform.translate(
                        offset: Offset(0, avatarVerticalOffset),
                        child: _buildSenderAvatar(),
                      )
                    : null,
              ),
            ],
            Flexible(
              child: Container(
                constraints: BoxConstraints(
                  maxWidth: MediaQuery.of(context).size.width * 0.65,
                ),
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 8,
                ),
                margin: _getMessageMargin(context),
                decoration: bubbleDecoration,
                child: Column(
                  crossAxisAlignment: isMe
                      ? CrossAxisAlignment.end
                      : CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    // Имя отправителя
                    if (isGroupChat && !isMe && senderName != null)
                      Padding(
                        padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
                        child: Text(
                          senderName ?? 'Неизвестный',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            color: _getUserColor(
                              message.senderId,
                              context,
                            ).withOpacity(0.8),
                            fontSize: 12,
                          ),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                    if (isGroupChat && !isMe && senderName != null)
                      const SizedBox(height: 4),

                    Text(
                      'Это сообщение не поддерживается в Вашей версии Komet. '
                      'Пожалуйста, обновитесь до последней версии. '
                      'Если Вы уже используете свежую версию приложения, '
                      'возможно, в сообщении используется нововведение, '
                      'которое пока не поддерживается.',
                      style: TextStyle(
                        color: textColor,
                        fontStyle: FontStyle.italic,
                        fontSize: 14,
                      ),
                      textAlign: TextAlign.left,
                    ),

                    const SizedBox(height: 8.0),
                    Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        if (isMe) ...[
                          if (message.isEdited) ...[
                            Text(
                              '(изменено)',
                              style: TextStyle(
                                fontSize: 10,
                                color: textColor.withOpacity(0.5),
                                fontStyle: FontStyle.italic,
                              ),
                            ),
                            const SizedBox(width: 6),
                          ],
                          Text(
                            _formatMessageTime(context, message.time),
                            style: TextStyle(
                              fontSize: 12,
                              color:
                                  Theme.of(context).brightness ==
                                      Brightness.dark
                                  ? const Color(0xFF9bb5c7)
                                  : const Color(0xFF6b7280),
                            ),
                          ),
                        ],
                        if (!isMe) ...[
                          Text(
                            _formatMessageTime(context, message.time),
                            style: TextStyle(
                              fontSize: 12,
                              color:
                                  Theme.of(context).brightness ==
                                      Brightness.dark
                                  ? const Color(0xFF9bb5c7)
                                  : const Color(0xFF6b7280),
                            ),
                          ),
                          if (message.isEdited) ...[
                            const SizedBox(width: 6),
                            Text(
                              '(изменено)',
                              style: TextStyle(
                                fontSize: 10,
                                color: textColor.withOpacity(0.5),
                                fontStyle: FontStyle.italic,
                              ),
                            ),
                          ],
                        ],
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildStickerOnlyMessage(BuildContext context) {
    final sticker = message.attaches.firstWhere((a) => a['_type'] == 'STICKER');
    final stickerSize = 170.0;

    final timeColor = Theme.of(context).brightness == Brightness.dark
        ? const Color(0xFF9bb5c7)
        : const Color(0xFF6b7280);

    return Column(
      crossAxisAlignment: isMe
          ? CrossAxisAlignment.end
          : CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: isMe
              ? MainAxisAlignment.end
              : MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            if (!isMe && isGroupChat && !isChannel) ...[
              SizedBox(
                width: 40,
                child: isLastInGroup
                    ? Transform.translate(
                        offset: Offset(0, avatarVerticalOffset),
                        child: _buildSenderAvatar(),
                      )
                    : null,
              ),
            ],
            Column(
              crossAxisAlignment: isMe
                  ? CrossAxisAlignment.end
                  : CrossAxisAlignment.start,
              children: [
                GestureDetector(
                  onTap: () => _openPhotoViewer(context, sticker),
                  child: ConstrainedBox(
                    constraints: BoxConstraints(
                      maxWidth: stickerSize,
                      maxHeight: stickerSize,
                    ),
                    child: _buildStickerImage(context, sticker),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 4, right: 6),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        _formatMessageTime(context, message.time),
                        style: TextStyle(fontSize: 12, color: timeColor),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildVideoCircleOnlyMessage(BuildContext context) {
    final video = message.attaches.firstWhere((a) => a['_type'] == 'VIDEO');
    final videoId = video['videoId'] as int?;
    final previewData = video['previewData'] as String?;
    final thumbnailUrl = video['url'] ?? video['baseUrl'] as String?;

    Uint8List? previewBytes;
    if (previewData != null && previewData.startsWith('data:')) {
      final idx = previewData.indexOf('base64,');
      if (idx != -1) {
        final b64 = previewData.substring(idx + 7);
        try {
          previewBytes = base64Decode(b64);
        } catch (_) {}
      }
    }

    String? highQualityThumbnailUrl;
    if (thumbnailUrl != null && thumbnailUrl.isNotEmpty) {
      highQualityThumbnailUrl = thumbnailUrl;
      if (!thumbnailUrl.contains('?')) {
        highQualityThumbnailUrl =
            '$thumbnailUrl?size=medium&quality=high&format=jpeg';
      } else {
        highQualityThumbnailUrl =
            '$thumbnailUrl&size=medium&quality=high&format=jpeg';
      }
    }

    final timeColor = Theme.of(context).brightness == Brightness.dark
        ? const Color(0xFF9bb5c7)
        : const Color(0xFF6b7280);

    Widget videoContent = Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
      child: Column(
        crossAxisAlignment: isMe
            ? CrossAxisAlignment.end
            : CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: isMe
                ? MainAxisAlignment.end
                : MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              Column(
                crossAxisAlignment: isMe
                    ? CrossAxisAlignment.end
                    : CrossAxisAlignment.start,
                children: [
                  if (videoId != null && chatId != null)
                    _buildVideoCirclePlayer(
                      context: context,
                      videoId: videoId,
                      messageId: message.id,
                      highQualityUrl: highQualityThumbnailUrl,
                      lowQualityBytes: previewBytes,
                    ),
                  Padding(
                    padding: const EdgeInsets.only(top: 4, right: 6),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text(
                          _formatMessageTime(context, message.time),
                          style: TextStyle(fontSize: 12, color: timeColor),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    );

    if (onReaction != null || (isMe && (onEdit != null || onDelete != null))) {
      videoContent = GestureDetector(
        onTapDown: (TapDownDetails details) {
          _showMessageContextMenu(context, details.globalPosition);
        },
        child: videoContent,
      );
    }

    return videoContent;
  }

  Widget _buildStickerImage(
    BuildContext context,
    Map<String, dynamic> sticker,
  ) {
    final url = sticker['url'] ?? sticker['baseUrl'];

    if (url is String && url.isNotEmpty) {
      if (url.startsWith('file://')) {
        final path = url.replaceFirst('file://', '');
        return Image.file(
          File(path),
          fit: BoxFit.contain,
          filterQuality: FilterQuality.high,
        );
      } else {
        return Image.network(
          url,
          fit: BoxFit.contain,
          filterQuality: FilterQuality.high,
          loadingBuilder: (context, child, loadingProgress) {
            if (loadingProgress == null) {
              return child;
            }
            return const Center(child: CircularProgressIndicator());
          },
          errorBuilder: (context, error, stackTrace) {
            return _imagePlaceholder();
          },
        );
      }
    }

    return _imagePlaceholder();
  }

  String? _extractFirstPhotoUrl(List<Map<String, dynamic>> attaches) {
    for (final a in attaches) {
      if (a['_type'] == 'PHOTO') {
        final dynamic maybe = a['url'] ?? a['baseUrl'];
        if (maybe is String && maybe.isNotEmpty) return maybe;
      }
    }
    return null;
  }

  List<Widget> _buildPhotosWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final photos = attaches.where((a) => a['_type'] == 'PHOTO').toList();
    final List<Widget> widgets = [];

    if (photos.isEmpty) return widgets;

    // Умная группировка фотографий
    widgets.add(
      _buildSmartPhotoGroup(context, photos, textColor, isUltraOptimized),
    );

    widgets.add(const SizedBox(height: 6));

    return widgets;
  }

  List<Widget> _buildVideosWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final videos = attaches.where((a) => a['_type'] == 'VIDEO').toList();
    final List<Widget> widgets = [];

    if (videos.isEmpty) return widgets;

    for (final video in videos) {
      // 1. Извлекаем все, что нам нужно
      final videoId = video['videoId'] as int?;
      final videoType = video['videoType'] as int?;
      final previewData = video['previewData'] as String?; // Блюр-превью
      final thumbnailUrl =
          video['url'] ?? video['baseUrl'] as String?; // HQ-превью URL

      // 2. Декодируем блюр-превью
      Uint8List? previewBytes;
      if (previewData != null && previewData.startsWith('data:')) {
        final idx = previewData.indexOf('base64,');
        if (idx != -1) {
          final b64 = previewData.substring(idx + 7);
          try {
            previewBytes = base64Decode(b64);
          } catch (_) {}
        }
      }

      // 3. Формируем URL для HQ-превью (как для фото)
      String? highQualityThumbnailUrl;
      if (thumbnailUrl != null && thumbnailUrl.isNotEmpty) {
        highQualityThumbnailUrl = thumbnailUrl;
        if (!thumbnailUrl.contains('?')) {
          highQualityThumbnailUrl =
              '$thumbnailUrl?size=medium&quality=high&format=jpeg';
        } else {
          highQualityThumbnailUrl =
              '$thumbnailUrl&size=medium&quality=high&format=jpeg';
        }
      }

      // 4. Создаем виджет
      if (videoId != null && chatId != null) {
        widgets.add(
          Padding(
            padding: const EdgeInsets.only(bottom: 4.0),
            child: _buildVideoPreview(
              context: context,
              videoId: videoId,
              messageId: message.id,
              highQualityUrl: highQualityThumbnailUrl,
              lowQualityBytes: previewBytes,
              videoType: videoType,
            ),
          ),
        );
      } else {
        // Заглушка, если вложение есть, а ID не найдены
        widgets.add(
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.black12,
            child: Row(
              children: [
                Icon(Icons.videocam_off, color: textColor),
                const SizedBox(width: 8),
                Text(
                  'Видео повреждено (нет ID)',
                  style: TextStyle(color: textColor),
                ),
              ],
            ),
          ),
        );
      }
    }

    widgets.add(const SizedBox(height: 6));
    return widgets;
  }

  List<Widget> _buildStickersWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final stickers = attaches.where((a) => a['_type'] == 'STICKER').toList();
    final List<Widget> widgets = [];

    if (stickers.isEmpty) return widgets;

    for (final sticker in stickers) {
      widgets.add(
        _buildStickerWidget(context, sticker, textColor, isUltraOptimized),
      );
      widgets.add(const SizedBox(height: 6));
    }

    return widgets;
  }

  Widget _buildStickerWidget(
    BuildContext context,
    Map<String, dynamic> sticker,
    Color textColor,
    bool isUltraOptimized,
  ) {
    // Стикеры обычно квадратные, около 200-250px
    final stickerSize = 170.0;

    return ConstrainedBox(
      constraints: BoxConstraints(
        maxWidth: stickerSize,
        maxHeight: stickerSize,
      ),
      child: GestureDetector(
        onTap: () => _openPhotoViewer(context, sticker),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(isUltraOptimized ? 8 : 12),
          child: _buildPhotoWidget(context, sticker),
        ),
      ),
    );
  }

  List<Widget> _buildCallsWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final calls = attaches.where((a) {
      final type = a['_type'];
      return type == 'CALL' || type == 'call';
    }).toList();
    final List<Widget> widgets = [];

    if (calls.isEmpty) return widgets;

    for (final call in calls) {
      widgets.add(
        _buildCallWidget(
          context,
          call,
          textColor,
          isUltraOptimized,
          messageTextOpacity,
        ),
      );
      widgets.add(const SizedBox(height: 6));
    }

    return widgets;
  }

  Widget _buildCallWidget(
    BuildContext context,
    Map<String, dynamic> callData,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final hangupType = callData['hangupType'] as String? ?? '';
    final callType = callData['callType'] as String? ?? 'AUDIO';
    final duration = callData['duration'] as int? ?? 0;
    final borderRadius = BorderRadius.circular(isUltraOptimized ? 8 : 12);

    String callText;
    IconData callIcon;
    Color callColor;

    // Определяем текст, иконку и цвет в зависимости от типа завершения звонка
    switch (hangupType) {
      case 'HUNGUP':
        // Звонок был завершен успешно
        final minutes = duration ~/ 60000;
        final seconds = (duration % 60000) ~/ 1000;
        final durationText = minutes > 0
            ? '$minutes:${seconds.toString().padLeft(2, '0')}'
            : '$seconds сек';

        final callTypeText = callType == 'VIDEO' ? 'Видеозвонок' : 'Звонок';
        callText = '$callTypeText, $durationText';
        callIcon = callType == 'VIDEO' ? Icons.videocam : Icons.call;
        callColor = Theme.of(context).colorScheme.primary;
        break;

      case 'MISSED':
        // Пропущенный звонок
        final callTypeText = callType == 'VIDEO'
            ? 'Пропущенный видеозвонок'
            : 'Пропущенный звонок';
        callText = callTypeText;
        callIcon = callType == 'VIDEO' ? Icons.videocam_off : Icons.call_missed;
        callColor = Theme.of(context).colorScheme.error;
        break;

      case 'CANCELED':
        // Звонок отменен
        final callTypeText = callType == 'VIDEO'
            ? 'Видеозвонок отменен'
            : 'Звонок отменен';
        callText = callTypeText;
        callIcon = callType == 'VIDEO' ? Icons.videocam_off : Icons.call_end;
        callColor = textColor.withOpacity(0.6);
        break;

      case 'REJECTED':
        // Звонок отклонен
        final callTypeText = callType == 'VIDEO'
            ? 'Видеозвонок отклонен'
            : 'Звонок отклонен';
        callText = callTypeText;
        callIcon = callType == 'VIDEO' ? Icons.videocam_off : Icons.call_end;
        callColor = textColor.withOpacity(0.6);
        break;

      default:
        // Неизвестный тип завершения
        callText = callType == 'VIDEO' ? 'Видеозвонок' : 'Звонок';
        callIcon = callType == 'VIDEO' ? Icons.videocam : Icons.call;
        callColor = textColor.withOpacity(0.6);
        break;
    }

    return Container(
      decoration: BoxDecoration(
        color: callColor.withOpacity(0.1),
        borderRadius: borderRadius,
        border: Border.all(color: callColor.withOpacity(0.3), width: 1),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          children: [
            // Call icon
            Container(
              width: 48,
              height: 48,
              decoration: BoxDecoration(
                color: callColor.withOpacity(0.2),
                borderRadius: BorderRadius.circular(8),
              ),
              child: Icon(callIcon, color: callColor, size: 24),
            ),
            const SizedBox(width: 12),
            // Call info
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    callText,
                    style: TextStyle(
                      color: callColor,
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  List<Widget> _buildFilesWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
    int? chatId,
  ) {
    final files = attaches.where((a) => a['_type'] == 'FILE').toList();
    final List<Widget> widgets = [];

    if (files.isEmpty) return widgets;

    for (final file in files) {
      final fileName = file['name'] ?? 'Файл';
      final fileSize = file['size'] as int? ?? 0;

      widgets.add(
        _buildFileWidget(
          context,
          fileName,
          fileSize,
          file,
          textColor,
          isUltraOptimized,
          chatId,
        ),
      );
      widgets.add(const SizedBox(height: 6));
    }

    return widgets;
  }

  Widget _buildFileWidget(
    BuildContext context,
    String fileName,
    int fileSize,
    Map<String, dynamic> fileData,
    Color textColor,
    bool isUltraOptimized,
    int? chatId,
  ) {
    final borderRadius = BorderRadius.circular(isUltraOptimized ? 8 : 12);

    // Get file extension
    final extension = _getFileExtension(fileName);
    final iconData = _getFileIcon(extension);

    // Format file size
    final sizeStr = _formatFileSize(fileSize);

    // Extract file data
    final fileId = fileData['fileId'] as int?;
    final token = fileData['token'] as String?;

    return GestureDetector(
      onTap: () =>
          _handleFileDownload(context, fileId, token, fileName, chatId),
      child: Container(
        decoration: BoxDecoration(
          color: textColor.withOpacity(0.05),
          borderRadius: borderRadius,
          border: Border.all(color: textColor.withOpacity(0.1), width: 1),
        ),
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            children: [
              // File icon
              Container(
                width: 48,
                height: 48,
                decoration: BoxDecoration(
                  color: textColor.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(
                  iconData,
                  color: textColor.withOpacity(0.8),
                  size: 24,
                ),
              ),
              const SizedBox(width: 12),
              // File info with progress
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      fileName,
                      style: TextStyle(
                        color: textColor,
                        fontSize: 14,
                        fontWeight: FontWeight.w500,
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    const SizedBox(height: 4),
                    if (fileId != null)
                      ValueListenableBuilder<double>(
                        valueListenable: FileDownloadProgressService()
                            .getProgress(fileId.toString()),
                        builder: (context, progress, child) {
                          if (progress < 0) {
                            // Not downloading
                            return Text(
                              sizeStr,
                              style: TextStyle(
                                color: textColor.withOpacity(0.6),
                                fontSize: 12,
                              ),
                            );
                          } else if (progress < 1.0) {
                            // Downloading
                            return Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                LinearProgressIndicator(
                                  value: progress,
                                  minHeight: 3,
                                  backgroundColor: textColor.withOpacity(0.1),
                                ),
                                const SizedBox(height: 4),
                                Text(
                                  '${(progress * 100).toStringAsFixed(0)}%',
                                  style: TextStyle(
                                    color: textColor.withOpacity(0.6),
                                    fontSize: 11,
                                  ),
                                ),
                              ],
                            );
                          } else {
                            // Completed
                            return Row(
                              children: [
                                Icon(
                                  Icons.check_circle,
                                  size: 12,
                                  color: Colors.green.withOpacity(0.8),
                                ),
                                const SizedBox(width: 4),
                                Text(
                                  'Загружено',
                                  style: TextStyle(
                                    color: Colors.green.withOpacity(0.8),
                                    fontSize: 11,
                                  ),
                                ),
                              ],
                            );
                          }
                        },
                      )
                    else
                      Text(
                        sizeStr,
                        style: TextStyle(
                          color: textColor.withOpacity(0.6),
                          fontSize: 12,
                        ),
                      ),
                  ],
                ),
              ),
              // Download icon
              if (fileId != null)
                ValueListenableBuilder<double>(
                  valueListenable: FileDownloadProgressService().getProgress(
                    fileId.toString(),
                  ),
                  builder: (context, progress, child) {
                    if (progress >= 0 && progress < 1.0) {
                      return const SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      );
                    }
                    return Icon(
                      Icons.download_outlined,
                      color: textColor.withOpacity(0.6),
                      size: 20,
                    );
                  },
                )
              else
                Icon(
                  Icons.download_outlined,
                  color: textColor.withOpacity(0.6),
                  size: 20,
                ),
            ],
          ),
        ),
      ),
    );
  }

  String _getFileExtension(String fileName) {
    final parts = fileName.split('.');
    if (parts.length > 1) {
      return parts.last.toLowerCase();
    }
    return '';
  }

  IconData _getFileIcon(String extension) {
    switch (extension) {
      case 'pdf':
        return Icons.picture_as_pdf;
      case 'doc':
      case 'docx':
        return Icons.description;
      case 'xls':
      case 'xlsx':
        return Icons.table_chart;
      case 'txt':
        return Icons.text_snippet;
      case 'zip':
      case 'rar':
      case '7z':
        return Icons.archive;
      case 'mp3':
      case 'wav':
      case 'flac':
        return Icons.audiotrack;
      case 'mp4':
      case 'avi':
      case 'mov':
        return Icons.video_file;
      case 'jpg':
      case 'jpeg':
      case 'png':
      case 'gif':
        return Icons.image;
      default:
        return Icons.insert_drive_file;
    }
  }

  String _formatFileSize(int bytes) {
    if (bytes < 1024) {
      return '$bytes B';
    } else if (bytes < 1024 * 1024) {
      return '${(bytes / 1024).toStringAsFixed(1)} KB';
    } else if (bytes < 1024 * 1024 * 1024) {
      return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
    } else {
      return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
    }
  }

  List<Widget> _buildAudioWithCaption(
    BuildContext context,
    List<Map<String, dynamic>> attaches,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final audioMessages = attaches.where((a) => a['_type'] == 'AUDIO').toList();
    final List<Widget> widgets = [];

    if (audioMessages.isEmpty) return widgets;

    for (final audio in audioMessages) {
      widgets.add(
        _buildAudioWidget(
          context,
          audio,
          textColor,
          isUltraOptimized,
          messageTextOpacity,
        ),
      );
      widgets.add(const SizedBox(height: 6));
    }

    return widgets;
  }

  Widget _buildAudioWidget(
    BuildContext context,
    Map<String, dynamic> audioData,
    Color textColor,
    bool isUltraOptimized,
    double messageTextOpacity,
  ) {
    final borderRadius = BorderRadius.circular(isUltraOptimized ? 8 : 12);
    final url = audioData['url'] as String?;
    final duration = audioData['duration'] as int? ?? 0;
    final wave = audioData['wave'] as String?;
    final audioId = audioData['audioId'] as int?;

    // Format duration
    final durationSeconds = (duration / 1000).round();
    final minutes = durationSeconds ~/ 60;
    final seconds = durationSeconds % 60;
    final durationText = '$minutes:${seconds.toString().padLeft(2, '0')}';

    return _AudioPlayerWidget(
      url: url ?? '',
      duration: duration,
      durationText: durationText,
      wave: wave,
      audioId: audioId,
      textColor: textColor,
      borderRadius: borderRadius,
      messageTextOpacity: messageTextOpacity,
    );
  }

  Future<void> _handleFileDownload(
    BuildContext context,
    int? fileId,
    String? token,
    String fileName,
    int? chatId,
  ) async {
    // 1. Проверяем fileId, он нужен в любом случае
    if (fileId == null) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
              'Не удалось загрузить информацию о файле (нет fileId)',
            ),
            backgroundColor: Colors.red,
          ),
        );
      }
      return;
    }

    try {
      final prefs = await SharedPreferences.getInstance();
      final fileIdMap = prefs.getStringList('file_id_to_path_map') ?? [];
      final fileIdString = fileId.toString();

      // Ищем запись для нашего fileId
      final mapping = fileIdMap.firstWhere(
        (m) => m.startsWith('$fileIdString:'),
        orElse: () => '', // Возвращаем пустую строку, если не найдено
      );

      if (mapping.isNotEmpty) {
        // Извлекаем путь из 'fileId:path/to/file'
        final filePath = mapping.substring(fileIdString.length + 1);
        final file = io.File(filePath);

        // Проверяем, существует ли файл физически
        if (await file.exists()) {
          print(
            'Файл $fileName (ID: $fileId) найден локально: $filePath. Открываем...',
          );
          // Файл существует, открываем его
          final result = await OpenFile.open(filePath);

          if (result.type != ResultType.done && context.mounted) {
            // Показываем ошибку, если не удалось открыть (например, нет приложения)
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Не удалось открыть файл: ${result.message}'),
                backgroundColor: Colors.red,
              ),
            );
          }
          return; // Важно: выходим из функции, чтобы не скачивать заново
        } else {
          // Файл был в списке, но удален. Очистим некорректную запись.
          print(
            'Файл $fileName (ID: $fileId) был в SharedPreferences, но удален. Начинаем загрузку.',
          );
          fileIdMap.remove(mapping);
          await prefs.setStringList('file_id_to_path_map', fileIdMap);
        }
      }
    } catch (e) {
      print('Ошибка при проверке локального файла: $e. Продолжаем загрузку...');
      // Если при проверке что-то пошло не так, просто продолжаем и скачиваем файл.
    }

    // Если файл не найден локально, продолжаем стандартную процедуру скачивания
    print(
      'Файл $fileName (ID: $fileId) не найден. Запрашиваем URL у сервера...',
    );

    if (token == null) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
              'Не удалось загрузить информацию о файле (нет token)',
            ),
            backgroundColor: Colors.red,
          ),
        );
      }
      return;
    }

    if (chatId == null) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('Не удалось определить чат'),
            backgroundColor: Colors.red,
          ),
        );
      }
      return;
    }

    try {
      // Request file URL from server using opcode 88
      final messageId = message.id;

      // Send request for file URL via WebSocket
      final seq = ApiService.instance.sendRawRequest(88, {
        "fileId": fileId,
        "chatId": chatId,
        "messageId": messageId,
      });

      if (seq == -1) {
        if (context.mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('Не удалось отправить запрос на получение файла'),
              backgroundColor: Colors.red,
            ),
          );
        }
        return;
      }

      // Wait for response with opcode 88
      final response = await ApiService.instance.messages
          .firstWhere(
            (msg) => msg['seq'] == seq && msg['opcode'] == 88,
            orElse: () => <String, dynamic>{},
          )
          .timeout(
            const Duration(seconds: 10),
            onTimeout: () => throw TimeoutException(
              'Превышено время ожидания ответа от сервера',
            ),
          );

      if (response.isEmpty || response['payload'] == null) {
        throw Exception('Не получен ответ от сервера');
      }

      final downloadUrl = response['payload']['url'] as String?;
      if (downloadUrl == null || downloadUrl.isEmpty) {
        throw Exception('Не получена ссылка на файл');
      }

      // Download file to Downloads folder with progress
      await _downloadFile(downloadUrl, fileName, fileId.toString(), context);
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Ошибка при скачивании файла: ${e.toString()}'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  Future<void> _downloadFile(
    String url,
    String fileName,
    String fileId,
    BuildContext context,
  ) async {
    // Download in background without blocking dialog
    _startBackgroundDownload(url, fileName, fileId, context);

    // Show immediate success snackbar
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Начато скачивание файла...'),
          duration: Duration(seconds: 2),
        ),
      );
    }
  }

  void _startBackgroundDownload(
    String url,
    String fileName,
    String fileId,
    BuildContext context,
  ) async {
    // Initialize progress
    FileDownloadProgressService().updateProgress(fileId, 0.0);

    try {
      // Get Downloads directory
      io.Directory? downloadDir;

      if (io.Platform.isAndroid) {
        downloadDir = await getExternalStorageDirectory();
      } else if (io.Platform.isIOS) {
        final directory = await getApplicationDocumentsDirectory();
        downloadDir = directory;
      } else if (io.Platform.isWindows || io.Platform.isLinux) {
        // For desktop platforms, use Downloads directory
        final homeDir =
            io.Platform.environment['HOME'] ??
            io.Platform.environment['USERPROFILE'] ??
            '';
        downloadDir = io.Directory('$homeDir/Downloads');
      } else {
        downloadDir = await getApplicationDocumentsDirectory();
      }

      if (downloadDir == null || !await downloadDir.exists()) {
        throw Exception('Downloads directory not found');
      }

      // Create the file path
      final filePath = '${downloadDir.path}/$fileName';
      final file = io.File(filePath);

      // Download the file with progress tracking
      final request = http.Request('GET', Uri.parse(url));
      final streamedResponse = await request.send();

      if (streamedResponse.statusCode != 200) {
        throw Exception(
          'Failed to download file: ${streamedResponse.statusCode}',
        );
      }

      final contentLength = streamedResponse.contentLength ?? 0;
      final bytes = <int>[];
      int received = 0;

      await for (final chunk in streamedResponse.stream) {
        bytes.addAll(chunk);
        received += chunk.length;

        // Update progress if content length is known
        if (contentLength > 0) {
          final progress = received / contentLength;
          FileDownloadProgressService().updateProgress(fileId, progress);
        }
      }

      // Write file to disk
      final data = Uint8List.fromList(bytes);
      await file.writeAsBytes(data);

      // Mark as completed
      FileDownloadProgressService().updateProgress(fileId, 1.0);

      // Save file path and fileId mapping to SharedPreferences for tracking
      final prefs = await SharedPreferences.getInstance();
      final List<String> downloadedFiles =
          prefs.getStringList('downloaded_files') ?? [];
      if (!downloadedFiles.contains(file.path)) {
        downloadedFiles.add(file.path);
        await prefs.setStringList('downloaded_files', downloadedFiles);
      }

      // Also save fileId -> filePath mapping to track downloaded files by fileId
      final fileIdMap = prefs.getStringList('file_id_to_path_map') ?? [];
      final mappingKey = '$fileId:${file.path}';
      if (!fileIdMap.contains(mappingKey)) {
        fileIdMap.add(mappingKey);
        await prefs.setStringList('file_id_to_path_map', fileIdMap);
      }

      // Show success message
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Файл сохранен: $fileName'),
            duration: const Duration(seconds: 3),
            action: SnackBarAction(label: 'OK', onPressed: () {}),
          ),
        );
      }
    } catch (e) {
      // Clear progress on error
      FileDownloadProgressService().clearProgress(fileId);

      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Ошибка при скачивании: ${e.toString()}'),
            backgroundColor: Colors.red,
            duration: const Duration(seconds: 3),
          ),
        );
      }
    }
  }

  Widget _buildSmartPhotoGroup(
    BuildContext context,
    List<Map<String, dynamic>> photos,
    Color textColor,
    bool isUltraOptimized,
  ) {
    final borderRadius = BorderRadius.circular(isUltraOptimized ? 4 : 12);

    switch (photos.length) {
      case 1:
        return _buildSinglePhoto(context, photos[0], borderRadius);
      case 2:
        return _buildTwoPhotos(context, photos, borderRadius);
      case 3:
        return _buildThreePhotos(context, photos, borderRadius);
      case 4:
        return _buildFourPhotos(context, photos, borderRadius);
      default:
        return _buildManyPhotos(context, photos, borderRadius);
    }
  }

  Widget _buildSinglePhoto(
    BuildContext context,
    Map<String, dynamic> photo,
    BorderRadius borderRadius,
  ) {
    return RepaintBoundary(
      child: GestureDetector(
        onTap: () => _openPhotoViewer(context, photo),
        child: ClipRRect(
          borderRadius: borderRadius,
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxHeight: 180, maxWidth: 250),
            child: _buildPhotoWidget(context, photo),
          ),
        ),
      ),
    );
  }

  Widget _buildTwoPhotos(
    BuildContext context,
    List<Map<String, dynamic>> photos,
    BorderRadius borderRadius,
  ) {
    return ConstrainedBox(
      constraints: const BoxConstraints(maxHeight: 180),
      child: Row(
        children: [
          Expanded(
            child: RepaintBoundary(
              child: GestureDetector(
                onTap: () => _openPhotoViewer(context, photos[0]),
                child: ClipRRect(
                  borderRadius: borderRadius,
                  child: _buildPhotoWidget(context, photos[0]),
                ),
              ),
            ),
          ),
          const SizedBox(width: 2),
          Expanded(
            child: RepaintBoundary(
              child: GestureDetector(
                onTap: () => _openPhotoViewer(context, photos[1]),
                child: ClipRRect(
                  borderRadius: borderRadius,
                  child: _buildPhotoWidget(context, photos[1]),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildThreePhotos(
    BuildContext context,
    List<Map<String, dynamic>> photos,
    BorderRadius borderRadius,
  ) {
    return ConstrainedBox(
      constraints: const BoxConstraints(maxHeight: 180),
      child: Row(
        children: [
          // Левая большая фотка
          Expanded(
            flex: 2,
            child: RepaintBoundary(
              child: GestureDetector(
                onTap: () => _openPhotoViewer(context, photos[0]),
                child: ClipRRect(
                  borderRadius: borderRadius,
                  child: _buildPhotoWidget(context, photos[0]),
                ),
              ),
            ),
          ),
          const SizedBox(width: 2),
          // Правая колонка с двумя маленькими
          Expanded(
            flex: 1,
            child: Column(
              children: [
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[1]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[1]),
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 2),
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[2]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[2]),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildFourPhotos(
    BuildContext context,
    List<Map<String, dynamic>> photos,
    BorderRadius borderRadius,
  ) {
    return ConstrainedBox(
      constraints: const BoxConstraints(maxHeight: 180),
      child: Column(
        children: [
          // Верхний ряд
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[0]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[0]),
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[1]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[1]),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 2),
          // Нижний ряд
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[2]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[2]),
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[3]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[3]),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildManyPhotos(
    BuildContext context,
    List<Map<String, dynamic>> photos,
    BorderRadius borderRadius,
  ) {
    // Для 5+ фотографий показываем сетку 2x2 + счетчик
    return ConstrainedBox(
      constraints: const BoxConstraints(maxHeight: 180),
      child: Column(
        children: [
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[0]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[0]),
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[1]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[1]),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 2),
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: RepaintBoundary(
                    child: GestureDetector(
                      onTap: () => _openPhotoViewer(context, photos[2]),
                      child: ClipRRect(
                        borderRadius: borderRadius,
                        child: _buildPhotoWidget(context, photos[2]),
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                Expanded(
                  child: Stack(
                    children: [
                      RepaintBoundary(
                        child: GestureDetector(
                          onTap: () => _openPhotoViewer(context, photos[3]),
                          child: ClipRRect(
                            borderRadius: borderRadius,
                            child: _buildPhotoWidget(context, photos[3]),
                          ),
                        ),
                      ),
                      if (photos.length > 4)
                        Positioned.fill(
                          child: Container(
                            decoration: BoxDecoration(
                              color: Colors.black.withOpacity(0.6),
                              borderRadius: borderRadius,
                            ),
                            child: Center(
                              child: Text(
                                '+${photos.length - 3}',
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ),
                          ),
                        ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  void _openPhotoViewer(BuildContext context, Map<String, dynamic> attach) {
    final url = attach['url'] ?? attach['baseUrl'];
    final preview = attach['previewData'];

    Widget child;
    if (url is String && url.isNotEmpty) {
      if (url.startsWith('file://')) {
        final path = url.replaceFirst('file://', '');
        child = Image.file(
          File(path),
          fit: BoxFit.contain,
          filterQuality: FilterQuality.high,
        );
      } else {
        String fullQualityUrl = url;
        if (!url.contains('?')) {
          fullQualityUrl = '$url?size=original&quality=high&format=original';
        } else {
          fullQualityUrl = '$url&size=original&quality=high&format=original';
        }
        child = _ProgressiveNetworkImage(
          url: fullQualityUrl,
          previewBytes: null,
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          fit: BoxFit.contain,
        );
      }
    } else if (preview is String && preview.startsWith('data:')) {
      final idx = preview.indexOf('base64,');
      if (idx != -1) {
        final b64 = preview.substring(idx + 7);
        try {
          final bytes = base64Decode(b64);
          child = Image.memory(bytes, fit: BoxFit.contain);
        } catch (_) {
          child = _imagePlaceholder();
        }
      } else {
        child = _imagePlaceholder();
      }
    } else {
      child = _imagePlaceholder();
    }

    // Используем навигатор для перехода на новый полноэкранный виджет
    Navigator.of(context).push(
      PageRouteBuilder(
        opaque: false, // Делаем страницу прозрачной для красивого перехода
        barrierColor: Colors.black,
        pageBuilder: (BuildContext context, _, __) {
          // Возвращаем наш новый экран просмотра
          return FullScreenPhotoViewer(imageChild: child, attach: attach);
        },
        // Добавляем плавное появление
        transitionsBuilder: (_, animation, __, page) {
          return FadeTransition(opacity: animation, child: page);
        },
      ),
    );
  }

  Widget _buildPhotoWidget(BuildContext context, Map<String, dynamic> attach) {
    // Сначала обрабатываем локальные данные (base64), если они есть.
    // Это обеспечивает мгновенный показ размытого превью.
    Uint8List? previewBytes;
    final preview = attach['previewData'];
    if (preview is String && preview.startsWith('data:')) {
      final idx = preview.indexOf('base64,');
      if (idx != -1) {
        final b64 = preview.substring(idx + 7);
        try {
          previewBytes = base64Decode(b64);
        } catch (_) {
          // Ошибка декодирования, ничего страшного
        }
      }
    }

    final url = attach['url'] ?? attach['baseUrl'];
    if (url is String && url.isNotEmpty) {
      // Обработка локальных файлов (если фото отправляется с устройства)
      if (url.startsWith('file://')) {
        final path = url.replaceFirst('file://', '');
        return Image.file(
          File(path),
          fit: BoxFit.cover,
          width: 220,
          filterQuality:
              FilterQuality.medium, // Используем среднее качество для превью
          gaplessPlayback: true,
          errorBuilder: (context, _, __) => _imagePlaceholder(),
        );
      }

      // Формируем специальный URL для предпросмотра в чате:
      // средний размер, высокое качество, формат JPEG для эффективности.
      String previewQualityUrl = url;
      if (!url.contains('?')) {
        previewQualityUrl = '$url?size=medium&quality=high&format=jpeg';
      } else {
        previewQualityUrl = '$url&size=medium&quality=high&format=jpeg';
      }

      final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
      final optimize =
          themeProvider.optimizeChats || themeProvider.ultraOptimizeChats;

      // Используем наш новый URL для загрузки качественного превью
      return _ProgressiveNetworkImage(
        key: ValueKey(previewQualityUrl), // Ключ по новому URL
        url: previewQualityUrl, // Передаем новый URL
        previewBytes:
            previewBytes, // Передаем размытую заглушку для мгновенного отображения
        width: 220,
        height: 160,
        fit: BoxFit.cover,
        keepAlive: !optimize,
        startDownloadNextFrame: deferImageLoading,
      );
    }

    // Если URL нет, но есть base64 данные, покажем их
    if (previewBytes != null) {
      return Image.memory(previewBytes, fit: BoxFit.cover, width: 180);
    }

    // В самом крайнем случае показываем стандартный плейсхолдер
    return _imagePlaceholder();
  }

  Widget _imagePlaceholder() {
    return Container(
      width: 220,
      height: 160,
      color: Colors.black12,
      alignment: Alignment.center,
      child: const Icon(Icons.image_outlined, color: Colors.black38),
    );
  }

  // Лёгкий прогрессивный загрузчик: показывает превью, тянет оригинал с прогрессом и кэширует в памяти процесса

  Color _getBubbleColor(
    bool isMe,
    ThemeProvider themeProvider,
    double messageOpacity,
  ) {
    final baseColor = isMe
        ? (themeProvider.myBubbleColor ?? const Color(0xFF2b5278))
        : (themeProvider.theirBubbleColor ?? const Color(0xFF182533));
    return baseColor.withOpacity(1.0 - messageOpacity);
  }

  Color _getTextColor(
    bool isMe,
    Color bubbleColor,
    double messageTextOpacity,
    BuildContext context,
  ) {
    final isDarkMode = Theme.of(context).brightness == Brightness.dark;

    if (isDarkMode) {
      return Colors.white;
    } else {
      return Colors.black;
    }
  }

  List<Widget> _buildMessageContentChildren(
    BuildContext context,
    Color textColor,
    double messageTextOpacity,
    bool isUltraOptimized,
    TextStyle linkStyle,
    TextStyle defaultTextStyle,
    double messageBorderRadius,
    Future<void> Function(LinkableElement) onOpenLink,
    VoidCallback onSenderNameTap,
  ) {
    return [
      if (isGroupChat && !isMe && senderName != null)
        MouseRegion(
          cursor: SystemMouseCursors.click,
          child: GestureDetector(
            onTap: onSenderNameTap,
            child: Padding(
              padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
              child: Text(
                senderName ?? 'Неизвестный',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  color: _getUserColor(
                    message.senderId,
                    context,
                  ).withOpacity(0.8),
                  fontSize: 12,
                ),
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ),
        ),
      if (isGroupChat && !isMe && senderName != null) const SizedBox(height: 4),
      if (message.isForwarded && message.link != null) ...[
        if (message.link is Map<String, dynamic>)
          _buildForwardedMessage(
            context,
            message.link as Map<String, dynamic>,
            textColor,
            messageTextOpacity,
            isUltraOptimized,
          ),
      ] else ...[
        if (message.isReply && message.link != null) ...[
          if (message.link is Map<String, dynamic>)
            _buildReplyPreview(
              context,
              message.link as Map<String, dynamic>,
              textColor,
              messageTextOpacity,
              isUltraOptimized,
              messageBorderRadius,
            ),
          const SizedBox(height: 8),
        ],
        if (message.attaches.isNotEmpty) ...[
          ..._buildCallsWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
          ),
          ..._buildAudioWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
          ),
          ..._buildPhotosWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
          ),
          ..._buildVideosWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
          ),
          ..._buildStickersWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
          ),
          ..._buildFilesWithCaption(
            context,
            message.attaches,
            textColor,
            isUltraOptimized,
            messageTextOpacity,
            chatId,
          ),
          const SizedBox(height: 6),
        ],
        if (message.text.isNotEmpty) ...[
          if (message.text.contains("welcome.saved.dialog.message"))
            Container(
              alignment: Alignment.center,
              child: Text(
                'Привет! Это твои избранные. Все написанное сюда попадёт прямиком к дяде Майору.',
                style: TextStyle(color: textColor, fontStyle: FontStyle.italic),
                textAlign: TextAlign.center,
              ),
            )
          else
            Linkify(
              text: message.text,
              style: defaultTextStyle,
              linkStyle: linkStyle,
              onOpen: onOpenLink,
              options: const LinkifyOptions(humanize: false),
              textAlign: TextAlign.left,
            ),
          if (message.reactionInfo != null) const SizedBox(height: 4),
        ],
      ],
      ..._buildInlineKeyboard(context, message.attaches, textColor),
      _buildReactionsWidget(context, textColor),
      const SizedBox(height: 8.0),
      Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (isMe) ...[
            if (message.attaches.any((a) => a['_type'] == 'PHOTO')) ...[
              Builder(
                builder: (context) {
                  final url = _extractFirstPhotoUrl(message.attaches);
                  if (url == null || url.startsWith('file://')) {
                    return const SizedBox.shrink();
                  }
                  final notifier = GlobalImageStore.progressFor(url);
                  return ValueListenableBuilder<double?>(
                    valueListenable: notifier,
                    builder: (context, value, _) {
                      if (value == null || value <= 0 || value >= 1) {
                        return const SizedBox.shrink();
                      }
                      return SizedBox(
                        width: 24,
                        height: 12,
                        child: LinearProgressIndicator(
                          value: value,
                          backgroundColor: Colors.transparent,
                          color: textColor.withOpacity(
                            0.7 * messageTextOpacity,
                          ),
                          minHeight: 3,
                        ),
                      );
                    },
                  );
                },
              ),
              const SizedBox(width: 8),
            ],
            if (message.isEdited) ...[
              Text(
                '(изменено)',
                style: TextStyle(
                  fontSize: 10,
                  color: textColor.withOpacity(0.5 * messageTextOpacity),
                  fontStyle: FontStyle.italic,
                ),
              ),
              const SizedBox(width: 6),
            ],
            Text(
              _formatMessageTime(context, message.time),
              style: TextStyle(
                fontSize: 12,
                color: Theme.of(context).brightness == Brightness.dark
                    ? const Color(0xFF9bb5c7)
                    : const Color(0xFF6b7280),
              ),
            ),
            if (readStatus != null) ...[
              const SizedBox(width: 4),
              Builder(
                builder: (context) {
                  final bool isRead = readStatus == MessageReadStatus.read;
                  final Color iconColor = isRead
                      ? (Theme.of(context).brightness == Brightness.dark
                            ? Colors.lightBlueAccent[100]!
                            : Colors.blue[600]!)
                      : (Theme.of(context).brightness == Brightness.dark
                            ? const Color(0xFF9bb5c7)
                            : const Color(0xFF6b7280));
                  if (readStatus == MessageReadStatus.sending) {
                    return _RotatingIcon(
                      icon: Icons.watch_later_outlined,
                      size: 16,
                      color: iconColor,
                    );
                  } else {
                    return Icon(
                      isRead ? Icons.done_all : Icons.done,
                      size: 16,
                      color: iconColor,
                    );
                  }
                },
              ),
            ],
          ],
          if (!isMe) ...[
            Text(
              _formatMessageTime(context, message.time),
              style: TextStyle(
                fontSize: 12,
                color: Theme.of(context).brightness == Brightness.dark
                    ? const Color(0xFF9bb5c7)
                    : const Color(0xFF6b7280),
              ),
            ),
            if (message.isEdited) ...[
              const SizedBox(width: 6),
              Text(
                '(изменено)',
                style: TextStyle(
                  fontSize: 10,
                  color: textColor.withOpacity(0.5 * messageTextOpacity),
                  fontStyle: FontStyle.italic,
                ),
              ),
            ],
          ],
        ],
      ),
    ];
  }

  BoxDecoration _createBubbleDecoration(
    Color bubbleColor,
    double messageBorderRadius,
    double messageShadowIntensity,
  ) {
    return BoxDecoration(
      color: bubbleColor,
      borderRadius: BorderRadius.circular(messageBorderRadius),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(messageShadowIntensity),
          blurRadius: 8,
          spreadRadius: 0,
          offset: const Offset(0, 2),
        ),
      ],
    );
  }

  Widget _buildMessageContentInner(
    BuildContext context,
    BoxDecoration? decoration,
    List<Widget> children,
  ) {
    return Container(
      constraints: BoxConstraints(
        maxWidth: MediaQuery.of(context).size.width * 0.65,
      ),
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      margin: _getMessageMargin(context),
      decoration: decoration,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: children,
      ),
    );
  }

  Color _getLinkColor(Color bubbleColor, bool isMe) {
    final isDark = BrightnessExtension(
      ThemeData.estimateBrightnessForColor(bubbleColor),
    ).isDark;
    if (isMe) {
      return isDark ? Colors.white : Colors.blue[700]!;
    }
    return Colors.blue[700]!;
  }

  Widget _buildSenderAvatar() {
    final senderContact = contactDetailsCache?[message.senderId];
    final avatarUrl = senderContact?.photoBaseUrl;
    final contactName = senderContact?.name ?? 'Участник ${message.senderId}';

    return Builder(
      builder: (context) => MouseRegion(
        cursor: SystemMouseCursors.click,
        child: GestureDetector(
          onTap: () => openUserProfileById(context, message.senderId),
          child: AvatarCacheService().getAvatarWidget(
            avatarUrl,
            userId: message.senderId,
            size: 32,
            fallbackText: contactName,
            backgroundColor: _getUserColor(message.senderId, context),
            textColor: Colors.white,
          ),
        ),
      ),
    );
  }
}

class GlobalImageStore {
  static final Map<String, Uint8List> _memory = {};
  static final Map<String, ValueNotifier<double?>> _progress = {};

  static Uint8List? getData(String url) => _memory[url];
  static void setData(String url, Uint8List bytes) {
    _memory[url] = bytes;
    progressFor(url).value = null;
  }

  static ValueNotifier<double?> progressFor(String url) {
    return _progress.putIfAbsent(url, () => ValueNotifier<double?>(null));
  }

  static void setProgress(String url, double? value) {
    progressFor(url).value = value;
  }
}

class _ProgressiveNetworkImage extends StatefulWidget {
  final String url;
  final Uint8List? previewBytes;
  final double width;
  final double height;
  final BoxFit fit;
  final bool startDownloadNextFrame;
  final bool keepAlive;
  const _ProgressiveNetworkImage({
    super.key,
    required this.url,
    required this.previewBytes,
    required this.width,
    required this.height,
    required this.fit,
    this.startDownloadNextFrame = false,
    this.keepAlive = true,
  });

  @override
  State<_ProgressiveNetworkImage> createState() =>
      _ProgressiveNetworkImageState();
}

class _ProgressiveNetworkImageState extends State<_ProgressiveNetworkImage>
    with AutomaticKeepAliveClientMixin {
  static final Map<String, Uint8List> _memoryCache = {};
  Uint8List? _fullBytes;
  double _progress = 0.0;
  bool _error = false;
  String? _diskPath;

  @override
  void initState() {
    super.initState();

    // [!code ++] (НОВЫЙ БЛОК)
    // Если URL пустой, нечего загружать.
    // Полагаемся только на previewBytes.
    if (widget.url.isEmpty) {
      return;
    }
    // [!code ++] (КОНЕЦ НОВОГО БЛОКА)

    // Если есть в глобальном кэше — используем сразу
    final cached = GlobalImageStore.getData(widget.url);
    if (cached != null) {
      _fullBytes = cached;
      // no return, продолжаем проверить диск на всякий
    }
    // Если есть в кэше — используем
    if (_memoryCache.containsKey(widget.url)) {
      _fullBytes = _memoryCache[widget.url];
    }
    if (widget.startDownloadNextFrame) {
      // Загружаем в следующем кадре
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) _tryLoadFromDiskThenDownload();
      });
    } else {
      _tryLoadFromDiskThenDownload();
    }
  }

  Future<void> _tryLoadFromDiskThenDownload() async {
    // [!code ++] (НОВЫЙ БЛОК)
    // Не пытаемся грузить, если URL пустой
    if (widget.url.isEmpty) {
      return;
    }
    // [!code ++] (КОНЕЦ НОВОГО БЛОКА)

    // Попытка прочитать из дискового кэша
    try {
      final dir = await getTemporaryDirectory();
      final name = crypto.md5.convert(widget.url.codeUnits).toString();
      final filePath = '${dir.path}/imgcache_$name';
      _diskPath = filePath;
      final f = io.File(filePath);
      if (await f.exists()) {
        final data = await f.readAsBytes();
        _memoryCache[widget.url] = data;
        GlobalImageStore.setData(widget.url, data);
        if (mounted) setState(() => _fullBytes = data);
        return; // нашли на диске, скачивать не надо
      }
    } catch (_) {}
    await _download();
  }

  Future<void> _download() async {
    try {
      final req = http.Request('GET', Uri.parse(widget.url));
      req.headers['User-Agent'] =
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';

      final resp = await req.send();
      if (resp.statusCode != 200) {
        setState(() => _error = true);
        return;
      }
      final contentLength = resp.contentLength ?? 0;
      final bytes = <int>[];
      int received = 0;
      await for (final chunk in resp.stream) {
        bytes.addAll(chunk);
        received += chunk.length;
        if (contentLength > 0) {
          final p = received / contentLength;
          _progress =
              p; // не дергаем setState, чтобы не создавать лишние перерисовки при slide
          GlobalImageStore.setProgress(widget.url, _progress);
        }
      }
      final data = Uint8List.fromList(bytes);
      _memoryCache[widget.url] = data;
      GlobalImageStore.setData(widget.url, data);
      // Пишем на диск
      try {
        final path = _diskPath;
        if (path != null) {
          final f = io.File(path);
          await f.writeAsBytes(data, flush: true);
        }
      } catch (_) {}
      if (mounted) setState(() => _fullBytes = data);
    } catch (_) {
      if (mounted) setState(() => _error = true);
    }
  }

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

  @override
  Widget build(BuildContext context) {
    super.build(context);
    final width = widget.width;
    final height = widget.height;
    if (_error) {
      return Container(
        width: width,
        height: height,
        color: Colors.black12,
        alignment: Alignment.center,
        child: const Icon(Icons.broken_image_outlined, color: Colors.black38),
      );
    }
    // Полное качество есть — показываем
    return RepaintBoundary(
      child: SizedBox(
        width: width,
        height: height,
        child: ClipRRect(
          borderRadius: BorderRadius.circular(
            0,
          ), // Упрощено для производительности
          child: Stack(
            fit: StackFit.expand,
            children: [
              // 1) Стабильный нижний слой — превью или нейтральный фон
              if (widget.previewBytes != null)
                Image.memory(
                  widget.previewBytes!,
                  fit: widget.fit,
                  filterQuality: FilterQuality.none,
                )
              else
                Container(color: Colors.black12),
              // 2) Верхний слой — оригинал. Он появляется, но не убирает превью, чтобы не мигать
              if (_fullBytes != null)
                Image.memory(
                  _fullBytes!,
                  fit: widget.fit,
                  filterQuality: FilterQuality.high,
                ),
              // нижний прогресс убран, чтобы не перерисовывать слой картинки во время slide;
              // прогресс выводится рядом со временем сообщения
            ],
          ),
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
  @override
  void didUpdateWidget(covariant _ProgressiveNetworkImage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.keepAlive != widget.keepAlive) {
      // Пересоберём keepAlive флаг
      updateKeepAlive();
    }
  }
}

extension BrightnessExtension on Brightness {
  bool get isDark => this == Brightness.dark;
}

class _CustomEmojiButton extends StatefulWidget {
  final Function(String) onCustomEmoji;

  const _CustomEmojiButton({required this.onCustomEmoji});

  @override
  State<_CustomEmojiButton> createState() => _CustomEmojiButtonState();
}

class _CustomEmojiButtonState extends State<_CustomEmojiButton>
    with TickerProviderStateMixin {
  late AnimationController _scaleController;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _scaleController = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );

    _scaleAnimation = Tween<double>(begin: 1.0, end: 1.15).animate(
      CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
    );
  }

  @override
  void dispose() {
    _scaleController.dispose();
    super.dispose();
  }

  // Логика нажатия упрощена
  void _handleTap() {
    // Анимация масштабирования для обратной связи
    _scaleController.forward().then((_) {
      _scaleController.reverse();
    });
    // Сразу открываем диалог
    _showCustomEmojiDialog();
  }

  void _showCustomEmojiDialog() {
    showDialog(
      context: context,
      builder: (context) => _CustomEmojiDialog(
        onEmojiSelected: (emoji) {
          widget.onCustomEmoji(emoji);
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: AnimatedBuilder(
        animation: _scaleController,
        builder: (context, child) {
          return Transform.scale(
            scale: _scaleAnimation.value,
            child: Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Theme.of(context).colorScheme.surfaceContainerHighest,
                borderRadius: BorderRadius.circular(20),
              ),
              // Стрелка заменена на иконку "добавить"
              child: Icon(
                Icons.add_reaction_outlined,
                size: 24,
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
          );
        },
      ),
    );
  }
}

class _CustomEmojiDialog extends StatefulWidget {
  final Function(String) onEmojiSelected;

  const _CustomEmojiDialog({required this.onEmojiSelected});

  @override
  State<_CustomEmojiDialog> createState() => _CustomEmojiDialogState();
}

class _CustomEmojiDialogState extends State<_CustomEmojiDialog> {
  final TextEditingController _controller = TextEditingController();
  String _selectedEmoji = '';

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      title: Row(
        children: [
          Icon(
            Icons.emoji_emotions,
            color: Theme.of(context).colorScheme.primary,
          ),
          const SizedBox(width: 8),
          const Text('Введите эмодзи'),
        ],
      ),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              border: Border.all(
                color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
              ),
            ),
            child: TextField(
              controller: _controller,
              maxLength: 1, // Только один символ
              decoration: InputDecoration(
                hintText: 'Введите эмодзи...',
                border: InputBorder.none,
                contentPadding: const EdgeInsets.all(16),
                counterText: '',
                hintStyle: TextStyle(
                  color: Theme.of(
                    context,
                  ).colorScheme.onSurface.withOpacity(0.6),
                ),
              ),
              onChanged: (value) {
                setState(() {
                  _selectedEmoji = value;
                });
              },
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 24),
            ),
          ),
          const SizedBox(height: 20),
          if (_selectedEmoji.isNotEmpty) ...[
            Container(
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [
                    Theme.of(context).colorScheme.primaryContainer,
                    Theme.of(
                      context,
                    ).colorScheme.primaryContainer.withOpacity(0.7),
                  ],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                ),
                borderRadius: BorderRadius.circular(16),
                border: Border.all(
                  color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
                  width: 2,
                ),
                boxShadow: [
                  BoxShadow(
                    color: Theme.of(
                      context,
                    ).colorScheme.primary.withOpacity(0.2),
                    blurRadius: 8,
                    offset: const Offset(0, 2),
                  ),
                ],
              ),
              child: Text(_selectedEmoji, style: const TextStyle(fontSize: 48)),
            ),
            const SizedBox(height: 20),
          ],
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('Отмена'),
        ),
        FilledButton.icon(
          onPressed: _selectedEmoji.isNotEmpty
              ? () {
                  widget.onEmojiSelected(_selectedEmoji);
                  Navigator.of(context).pop();
                }
              : null,
          icon: const Icon(Icons.add),
          label: const Text('Добавить'),
          style: FilledButton.styleFrom(
            backgroundColor: Theme.of(context).colorScheme.primary,
            foregroundColor: Theme.of(context).colorScheme.onPrimary,
          ),
        ),
      ],
    );
  }
}

class _MessageContextMenu extends StatefulWidget {
  final Message message;
  final Offset position;
  final VoidCallback? onReply;
  final VoidCallback? onEdit;
  final VoidCallback? onDeleteForMe;
  final VoidCallback? onDeleteForAll;
  final Function(String)? onReaction;
  final VoidCallback? onRemoveReaction;
  final VoidCallback? onForward;
  final bool canEditMessage;
  final bool hasUserReaction;
  final bool isChannel;

  const _MessageContextMenu({
    required this.message,
    required this.position,
    this.onReply,
    this.onEdit,
    this.onDeleteForMe,
    this.onDeleteForAll,
    this.onReaction,
    this.onRemoveReaction,
    this.onForward,
    required this.canEditMessage,
    required this.hasUserReaction,
    this.isChannel = false,
  });

  @override
  _MessageContextMenuState createState() => _MessageContextMenuState();
}

class _MessageContextMenuState extends State<_MessageContextMenu>
    with SingleTickerProviderStateMixin {
  bool _isEmojiListExpanded = false;

  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;

  // Короткий список для быстрого доступа
  static const List<String> _quickReactions = [
    '👍',
    '❤️',
    '😂',
    '🔥',
    '👏',
    '🤔',
  ];

  // Полный список всех реакций
  static const List<String> _allReactions = [
    '👍',
    '❤️',
    '😂',
    '🔥',
    '👏',
    '👌',
    '🎉',
    '🥰',
    '😍',
    '🙏',
    '🤔',
    '🤯',
    '💯',
    '⚡️',
    '🤟',
    '🌚',
    '🌝',
    '🥱',
    '🤣',
    '🫠',
    '🫡',
    '🐱',
    '💋',
    '😘',
    '🐶',
    '🤝',
    '⭐️',
    '🍷',
    '🍑',
    '😁',
    '🤷‍♀️',
    '🤷‍♂️',
    '👩‍❤️‍👨',
    '🦄',
    '👻',
    '🗿',
    '❤️‍🩹',
    '🛑',
    '⛄️',
    '❓',
    '🙄',
    '❗️',
    '😉',
    '😳',
    '🥳',
    '😎',
    '💪',
    '👀',
    '🤞',
    '🤤',
    '🤪',
    '🤩',
    '😴',
    '😐',
    '😇',
    '🖤',
    '👑',
    '👋',
    '👁️',
  ];

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 150),
    );

    _scaleAnimation = CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeOutCubic,
    );

    _animationController.forward();
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void _onCopy() {
    Clipboard.setData(ClipboardData(text: widget.message.text));
    Navigator.of(context).pop();
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Сообщение скопировано'),
        behavior: SnackBarBehavior.floating,
        duration: Duration(seconds: 2),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final themeProvider = Provider.of<ThemeProvider>(context);
    final screenSize = MediaQuery.of(context).size;

    const menuWidth = 250.0;
    final double estimatedMenuHeight = _isEmojiListExpanded ? 320.0 : 250.0;

    double left = widget.position.dx - (menuWidth / 4);
    if (left + menuWidth > screenSize.width) {
      left = screenSize.width - menuWidth - 16;
    }
    if (left < 16) {
      left = 16;
    }

    double top = widget.position.dy;
    if (top + estimatedMenuHeight > screenSize.height) {
      top = widget.position.dy - estimatedMenuHeight - 10;
    }

    return Scaffold(
      backgroundColor: Colors.black.withOpacity(0.1),
      body: Stack(
        children: [
          Positioned.fill(
            child: GestureDetector(
              onTap: () => Navigator.of(context).pop(),
              child: Container(color: Colors.transparent),
            ),
          ),
          Positioned(
            top: top,
            left: left,
            child: ScaleTransition(
              scale: _scaleAnimation,
              alignment: Alignment.topCenter,
              child: ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: BackdropFilter(
                  filter: ImageFilter.blur(
                    sigmaX: themeProvider.messageMenuBlur,
                    sigmaY: themeProvider.messageMenuBlur,
                  ),
                  child: Card(
                    elevation: 8,
                    color: theme.colorScheme.surface.withOpacity(
                      themeProvider.messageMenuOpacity,
                    ),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(16),
                    ),
                    child: Container(
                      width: menuWidth,
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          AnimatedSize(
                            duration: const Duration(milliseconds: 250),
                            curve: Curves.easeInOut,
                            child: _buildEmojiSection(),
                          ),
                          const Divider(height: 12),
                          _buildActionsSection(theme),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildEmojiSection() {
    if (_isEmojiListExpanded) {
      return SizedBox(
        height: 150,
        child: GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 5,
            mainAxisSpacing: 8,
            crossAxisSpacing: 8,
          ),
          itemCount: _allReactions.length,
          itemBuilder: (context, index) {
            final emoji = _allReactions[index];
            return GestureDetector(
              onTap: () {
                Navigator.pop(context);
                widget.onReaction?.call(emoji);
              },
              child: Center(
                child: Text(emoji, style: const TextStyle(fontSize: 28)),
              ),
            );
          },
        ),
      );
    } else {
      return Wrap(
        spacing: 8,
        runSpacing: 4,
        alignment: WrapAlignment.center,
        children: [
          ..._quickReactions.map(
            (emoji) => GestureDetector(
              onTap: () {
                Navigator.pop(context);
                widget.onReaction?.call(emoji);
              },
              child: Padding(
                padding: const EdgeInsets.all(4.0),
                child: Text(emoji, style: const TextStyle(fontSize: 28)),
              ),
            ),
          ),
          IconButton(
            icon: const Icon(Icons.add_circle_outline, size: 28),
            onPressed: () => setState(() => _isEmojiListExpanded = true),
            tooltip: 'Больше реакций',
          ),
        ],
      );
    }
  }

  Widget _buildActionsSection(ThemeData theme) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (widget.message.text.isNotEmpty)
          _buildActionButton(
            icon: Icons.copy_rounded,
            text: 'Копировать',
            onTap: _onCopy,
          ),
        if (widget.onReply != null && !widget.isChannel)
          _buildActionButton(
            icon: Icons.reply_rounded,
            text: 'Ответить',
            onTap: () {
              Navigator.pop(context);
              widget.onReply!();
            },
          ),
        if (widget.onForward != null)
          _buildActionButton(
            icon: Icons.forward_rounded,
            text: 'Переслать',
            onTap: () {
              Navigator.pop(context);
              widget.onForward!();
            },
          ),
        if (widget.onEdit != null)
          _buildActionButton(
            icon: widget.canEditMessage
                ? Icons.edit_rounded
                : Icons.edit_off_rounded,
            text: 'Редактировать',
            onTap: widget.canEditMessage
                ? () {
                    Navigator.pop(context);
                    widget.onEdit!();
                  }
                : null,
            color: widget.canEditMessage ? null : Colors.grey,
          ),
        if (widget.hasUserReaction && widget.onRemoveReaction != null)
          _buildActionButton(
            icon: Icons.remove_circle_outline_rounded,
            text: 'Убрать реакцию',
            color: theme.colorScheme.error,
            onTap: () {
              Navigator.pop(context);
              widget.onRemoveReaction!();
            },
          ),
        if (widget.onDeleteForMe != null)
          _buildActionButton(
            icon: Icons.person_remove_rounded,
            text: 'Удалить у меня',
            color: theme.colorScheme.error,
            onTap: () {
              Navigator.pop(context);
              widget.onDeleteForMe!();
            },
          ),
        if (widget.onDeleteForAll != null)
          _buildActionButton(
            icon: Icons.delete_forever_rounded,
            text: 'Удалить у всех',
            color: theme.colorScheme.error,
            onTap: () {
              Navigator.pop(context);
              widget.onDeleteForAll!();
            },
          ),
      ],
    );
  }

  Widget _buildActionButton({
    required IconData icon,
    required String text,
    required VoidCallback? onTap,
    Color? color,
  }) {
    return InkWell(
      onTap: onTap,
      borderRadius: BorderRadius.circular(8),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 10.0),
        child: Row(
          children: [
            Icon(
              icon,
              size: 20,
              color: color ?? Theme.of(context).iconTheme.color,
            ),
            const SizedBox(width: 12),
            Text(
              text,
              style: TextStyle(
                color: color,
                fontSize: 15,
                fontWeight: onTap == null ? FontWeight.normal : FontWeight.w500,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class FullScreenPhotoViewer extends StatefulWidget {
  final Widget imageChild;
  final Map<String, dynamic>? attach;

  const FullScreenPhotoViewer({
    super.key,
    required this.imageChild,
    this.attach,
  });

  @override
  State<FullScreenPhotoViewer> createState() => _FullScreenPhotoViewerState();
}

class _FullScreenPhotoViewerState extends State<FullScreenPhotoViewer> {
  late final TransformationController _transformationController;
  // Переменная для контроля, можно ли двигать изображение
  bool _isPanEnabled = false;

  @override
  void initState() {
    super.initState();
    _transformationController = TransformationController();
    // "Слушаем" изменения зума
    _transformationController.addListener(_onTransformChanged);
  }

  @override
  void dispose() {
    _transformationController.removeListener(_onTransformChanged);
    _transformationController.dispose();
    super.dispose();
  }

  void _onTransformChanged() {
    // Получаем текущий масштаб
    final currentScale = _transformationController.value.getMaxScaleOnAxis();
    // Разрешаем двигать, только если масштаб больше 1
    final shouldPan = currentScale > 1.0;

    // Обновляем состояние, только если оно изменилось
    if (shouldPan != _isPanEnabled) {
      setState(() {
        _isPanEnabled = shouldPan;
      });
    }
  }

  Future<void> _downloadPhoto() async {
    if (widget.attach == null) return;

    try {
      // Get Downloads directory
      io.Directory? downloadDir;

      if (io.Platform.isAndroid) {
        final directory = await getExternalStorageDirectory();
        if (directory != null) {
          downloadDir = io.Directory(
            '${directory.path.split('Android')[0]}Download',
          );
          if (!await downloadDir.exists()) {
            downloadDir = io.Directory(
              '${directory.path.split('Android')[0]}Downloads',
            );
          }
        }
      } else if (io.Platform.isIOS) {
        final directory = await getApplicationDocumentsDirectory();
        downloadDir = directory;
      } else if (io.Platform.isWindows || io.Platform.isLinux) {
        final homeDir =
            io.Platform.environment['HOME'] ??
            io.Platform.environment['USERPROFILE'] ??
            '';
        downloadDir = io.Directory('$homeDir/Downloads');
      } else {
        downloadDir = await getApplicationDocumentsDirectory();
      }

      if (downloadDir == null || !await downloadDir.exists()) {
        throw Exception('Downloads directory not found');
      }

      // Get photo URL
      final url = widget.attach!['url'] ?? widget.attach!['baseUrl'];
      if (url == null || url.isEmpty) {
        throw Exception('Photo URL not found');
      }

      // Extract file extension from URL or use .jpg as default
      String extension = 'jpg';
      final uri = Uri.tryParse(url);
      if (uri != null && uri.pathSegments.isNotEmpty) {
        final lastSegment = uri.pathSegments.last;
        final extMatch = RegExp(r'\.([a-zA-Z0-9]+)$').firstMatch(lastSegment);
        if (extMatch != null) {
          extension = extMatch.group(1)!;
        }
      }

      // Generate filename with timestamp
      final timestamp = DateTime.now().millisecondsSinceEpoch;
      final fileName = 'photo_$timestamp.$extension';
      final filePath = '${downloadDir.path}/$fileName';
      final file = io.File(filePath);

      // Download the image
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
        await file.writeAsBytes(response.bodyBytes);

        // Save to SharedPreferences
        final prefs = await SharedPreferences.getInstance();
        final List<String> downloadedFiles =
            prefs.getStringList('downloaded_files') ?? [];
        if (!downloadedFiles.contains(filePath)) {
          downloadedFiles.add(filePath);
          await prefs.setStringList('downloaded_files', downloadedFiles);
        }

        if (context.mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Фото сохранено: $fileName'),
              duration: const Duration(seconds: 3),
            ),
          );
        }
      } else {
        throw Exception('Failed to download photo: ${response.statusCode}');
      }
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Ошибка при скачивании фото: ${e.toString()}'),
            backgroundColor: Colors.red,
            duration: const Duration(seconds: 3),
          ),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        children: [
          GestureDetector(
            onTap: () {
              Navigator.of(context).pop();
            },
            child: InteractiveViewer(
              transformationController: _transformationController,
              panEnabled: _isPanEnabled,
              boundaryMargin: EdgeInsets.zero,
              minScale: 1.0,
              maxScale: 5.0,
              child: Center(child: widget.imageChild),
            ),
          ),
          // Top bar with close button and download button
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  IconButton(
                    icon: const Icon(Icons.close, color: Colors.white),
                    onPressed: () => Navigator.of(context).pop(),
                  ),
                  if (widget.attach != null)
                    IconButton(
                      icon: const Icon(Icons.download, color: Colors.white),
                      onPressed: _downloadPhoto,
                      tooltip: 'Скачать фото',
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _RotatingIcon extends StatefulWidget {
  final IconData icon;
  final double size;
  final Color color;

  const _RotatingIcon({
    required this.icon,
    required this.size,
    required this.color,
  });

  @override
  State<_RotatingIcon> createState() => _RotatingIconState();
}

class _RotatingIconState extends State<_RotatingIcon>
    with SingleTickerProviderStateMixin {
  // Важно добавить 'with SingleTickerProviderStateMixin'
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      // Длительность одного оборота (2 секунды)
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(); // Запускаем анимацию на бесконечное повторение
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // RotationTransition - это виджет, который вращает своего "ребенка"
    return RotationTransition(
      turns: _controller, // Анимация вращения
      child: Icon(widget.icon, size: widget.size, color: widget.color),
    );
  }
}

class _AudioPlayerWidget extends StatefulWidget {
  final String url;
  final int duration;
  final String durationText;
  final String? wave;
  final int? audioId;
  final Color textColor;
  final BorderRadius borderRadius;
  final double messageTextOpacity;

  const _AudioPlayerWidget({
    required this.url,
    required this.duration,
    required this.durationText,
    this.wave,
    this.audioId,
    required this.textColor,
    required this.borderRadius,
    required this.messageTextOpacity,
  });

  @override
  State<_AudioPlayerWidget> createState() => _AudioPlayerWidgetState();
}

class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> {
  late AudioPlayer _audioPlayer;
  bool _isPlaying = false;
  bool _isLoading = false;
  bool _isCompleted = false;
  Duration _position = Duration.zero;
  Duration _totalDuration = Duration.zero;
  List<int>? _waveformData;

  @override
  void initState() {
    super.initState();
    _audioPlayer = AudioPlayer();
    _totalDuration = Duration(milliseconds: widget.duration);

    if (widget.wave != null && widget.wave!.isNotEmpty) {
      _decodeWaveform(widget.wave!);
    }

    if (widget.url.isNotEmpty) {
      _preCacheAudio();
    }

    _audioPlayer.playerStateStream.listen((state) {
      if (mounted) {
        final wasCompleted = _isCompleted;
        setState(() {
          _isPlaying = state.playing;
          _isLoading =
              state.processingState == ProcessingState.loading ||
              state.processingState == ProcessingState.buffering;
          _isCompleted = state.processingState == ProcessingState.completed;
        });

        if (state.processingState == ProcessingState.completed &&
            !wasCompleted) {
          _audioPlayer.pause();
        }
      }
    });

    _audioPlayer.positionStream.listen((position) {
      if (mounted) {
        final reachedEnd =
            _totalDuration.inMilliseconds > 0 &&
            position.inMilliseconds >= _totalDuration.inMilliseconds - 50 &&
            _isPlaying;

        if (reachedEnd) {
          _audioPlayer.pause();
        }

        setState(() {
          _position = position;
          if (reachedEnd) {
            _isPlaying = false;
            _isCompleted = true;
          }
        });
      }
    });

    _audioPlayer.durationStream.listen((duration) {
      if (mounted && duration != null) {
        setState(() {
          _totalDuration = duration;
        });
      }
    });
  }

  void _decodeWaveform(String waveBase64) {
    try {
      String base64Data = waveBase64;
      if (waveBase64.contains(',')) {
        base64Data = waveBase64.split(',')[1];
      }

      final bytes = base64Decode(base64Data);
      _waveformData = bytes.toList();
    } catch (e) {
      print('Error decoding waveform: $e');
      _waveformData = null;
    }
  }

  Future<void> _preCacheAudio() async {
    try {
      final cacheService = CacheService();
      final hasCached = await cacheService.hasCachedAudioFile(
        widget.url,
        customKey: widget.audioId?.toString(),
      );
      if (!hasCached) {
        print('Pre-caching audio: ${widget.url}');
        final cachedPath = await cacheService.cacheAudioFile(
          widget.url,
          customKey: widget.audioId?.toString(),
        );
        if (cachedPath != null) {
          print('Audio pre-cached successfully: $cachedPath');
        } else {
          print('Failed to pre-cache audio (no internet?): ${widget.url}');
        }
      } else {
        print('Audio already cached: ${widget.url}');
      }
    } catch (e) {
      print('Error pre-caching audio: $e');
    }
  }

  Future<void> _togglePlayPause() async {
    if (_isLoading) return;

    try {
      if (_isPlaying) {
        await _audioPlayer.pause();
      } else {
        if (_isCompleted ||
            (_totalDuration.inMilliseconds > 0 &&
                _position.inMilliseconds >=
                    _totalDuration.inMilliseconds - 100)) {
          await _audioPlayer.stop();
          await _audioPlayer.seek(Duration.zero);
          if (mounted) {
            setState(() {
              _isCompleted = false;
              _isPlaying = false;
              _position = Duration.zero;
            });
          }
          await Future.delayed(const Duration(milliseconds: 150));
        }

        if (_audioPlayer.processingState == ProcessingState.idle) {
          if (widget.url.isNotEmpty) {
            final cacheService = CacheService();
            var cachedFile = await cacheService.getCachedAudioFile(
              widget.url,
              customKey: widget.audioId?.toString(),
            );

            if (cachedFile != null && await cachedFile.exists()) {
              print('Using cached audio file: ${cachedFile.path}');
              await _audioPlayer.setFilePath(cachedFile.path);
            } else {
              print('Audio not cached, playing from URL: ${widget.url}');
              try {
                await _audioPlayer.setUrl(widget.url);

                cacheService
                    .cacheAudioFile(
                      widget.url,
                      customKey: widget.audioId?.toString(),
                    )
                    .then((cachedPath) {
                      if (cachedPath != null) {
                        print('Audio cached in background: $cachedPath');
                      } else {
                        print('Failed to cache audio in background');
                      }
                    })
                    .catchError((e) {
                      print('Error caching audio in background: $e');
                    });
              } catch (e) {
                print('Error setting audio URL: $e');
                if (mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(
                        'Не удалось загрузить аудио: ${e.toString()}',
                      ),
                      backgroundColor: Colors.red,
                      duration: const Duration(seconds: 3),
                    ),
                  );
                }
                return;
              }
            }
          }
        }
        await _audioPlayer.play();
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Ошибка воспроизведения: $e'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  Future<void> _seek(Duration position) async {
    await _audioPlayer.seek(position);
    if (mounted) {
      setState(() {
        _isCompleted = false;
      });
    }
  }

  String _formatDuration(Duration duration) {
    final minutes = duration.inMinutes;
    final seconds = duration.inSeconds % 60;
    return '$minutes:${seconds.toString().padLeft(2, '0')}';
  }

  @override
  void dispose() {
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final progress = _totalDuration.inMilliseconds > 0
        ? _position.inMilliseconds / _totalDuration.inMilliseconds
        : 0.0;

    return Container(
      decoration: BoxDecoration(
        color: widget.textColor.withOpacity(0.05),
        borderRadius: widget.borderRadius,
        border: Border.all(color: widget.textColor.withOpacity(0.1), width: 1),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          children: [
            GestureDetector(
              onTap: _togglePlayPause,
              child: Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  color: widget.textColor.withOpacity(0.1),
                  shape: BoxShape.circle,
                ),
                child: _isLoading
                    ? const SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                    : Icon(
                        _isPlaying ? Icons.pause : Icons.play_arrow,
                        color: widget.textColor.withOpacity(
                          0.8 * widget.messageTextOpacity,
                        ),
                        size: 24,
                      ),
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (_waveformData != null && _waveformData!.isNotEmpty)
                    SizedBox(
                      height: 30,
                      child: CustomPaint(
                        painter: _WaveformPainter(
                          waveform: _waveformData!,
                          progress: progress,
                          color: widget.textColor.withOpacity(
                            0.6 * widget.messageTextOpacity,
                          ),
                          progressColor: widget.textColor.withOpacity(
                            0.9 * widget.messageTextOpacity,
                          ),
                        ),
                        child: GestureDetector(
                          onTapDown: (details) {
                            final RenderBox box =
                                context.findRenderObject() as RenderBox;
                            final localPosition = details.localPosition;
                            final tapProgress =
                                localPosition.dx / box.size.width;
                            final newPosition = Duration(
                              milliseconds:
                                  (_totalDuration.inMilliseconds * tapProgress)
                                      .round(),
                            );
                            _seek(newPosition);
                          },
                        ),
                      ),
                    )
                  else
                    ClipRRect(
                      borderRadius: BorderRadius.circular(2),
                      child: LinearProgressIndicator(
                        value: progress,
                        backgroundColor: widget.textColor.withOpacity(0.1),
                        valueColor: AlwaysStoppedAnimation<Color>(
                          widget.textColor.withOpacity(
                            0.6 * widget.messageTextOpacity,
                          ),
                        ),
                        minHeight: 3,
                      ),
                    ),
                  const SizedBox(height: 4),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(
                        _formatDuration(_position),
                        style: TextStyle(
                          color: widget.textColor.withOpacity(
                            0.7 * widget.messageTextOpacity,
                          ),
                          fontSize: 12,
                        ),
                      ),
                      Text(
                        widget.durationText,
                        style: TextStyle(
                          color: widget.textColor.withOpacity(
                            0.7 * widget.messageTextOpacity,
                          ),
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _WaveformPainter extends CustomPainter {
  final List<int> waveform;
  final double progress;
  final Color color;
  final Color progressColor;

  _WaveformPainter({
    required this.waveform,
    required this.progress,
    required this.color,
    required this.progressColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    if (waveform.isEmpty) return;

    final paint = Paint()
      ..strokeWidth = 2
      ..strokeCap = StrokeCap.round;

    final barWidth = size.width / waveform.length;
    final maxAmplitude = waveform.reduce((a, b) => a > b ? a : b).toDouble();

    for (int i = 0; i < waveform.length; i++) {
      final amplitude = waveform[i].toDouble();
      final normalizedAmplitude = maxAmplitude > 0
          ? amplitude / maxAmplitude
          : 0.0;
      final barHeight = normalizedAmplitude * size.height * 0.8;
      final x = i * barWidth + barWidth / 2;
      final isPlayed = i / waveform.length < progress;

      paint.color = isPlayed ? progressColor : color;

      canvas.drawLine(
        Offset(x, size.height / 2 - barHeight / 2),
        Offset(x, size.height / 2 + barHeight / 2),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(_WaveformPainter oldDelegate) {
    return oldDelegate.progress != progress || oldDelegate.waveform != waveform;
  }
}

class _VideoCirclePlayer extends StatefulWidget {
  final int videoId;
  final String messageId;
  final int chatId;
  final String? highQualityUrl;
  final Uint8List? lowQualityBytes;

  const _VideoCirclePlayer({
    required this.videoId,
    required this.messageId,
    required this.chatId,
    this.highQualityUrl,
    this.lowQualityBytes,
  });

  @override
  State<_VideoCirclePlayer> createState() => _VideoCirclePlayerState();
}

class _VideoCirclePlayerState extends State<_VideoCirclePlayer> {
  VideoPlayerController? _controller;
  bool _isLoading = true;
  bool _hasError = false;
  bool _isPlaying = false;
  bool _isUserTapped = false;

  @override
  void initState() {
    super.initState();
    _loadVideo();
  }

  Future<void> _loadVideo() async {
    try {
      final videoUrl = await ApiService.instance.getVideoUrl(
        widget.videoId,
        widget.chatId,
        widget.messageId,
      );

      if (!mounted) return;

      _controller = VideoPlayerController.networkUrl(
        Uri.parse(videoUrl),
        httpHeaders: const {
          'User-Agent':
              'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        },
      );

      await _controller!.initialize();

      if (!mounted) return;

      _controller!.setLooping(true);
      _controller!.setVolume(0.0);
      _controller!.play();

      setState(() {
        _isLoading = false;
        _isPlaying = true;
        _isUserTapped = false;
      });
    } catch (e) {
      print('❌ [VideoCirclePlayer] Error loading video: $e');
      if (mounted) {
        setState(() {
          _hasError = true;
          _isLoading = false;
        });
      }
    }
  }

  void _videoListener() {
    if (_controller == null || !_isUserTapped) return;

    if (_controller!.value.position >= _controller!.value.duration &&
        _controller!.value.duration > Duration.zero) {
      _controller!.pause();
      _controller!.seekTo(Duration.zero);
      if (mounted) {
        setState(() {
          _isPlaying = false;
        });
      }
    }
  }

  void _togglePlayPause() {
    if (_controller == null) return;

    if (!_isUserTapped) {
      _controller!.addListener(_videoListener);
      _controller!.setLooping(false);
      _controller!.setVolume(1.0);

      _controller!.seekTo(Duration.zero);

      setState(() {
        _isUserTapped = true;
        _isPlaying = true;
      });

      _controller!.play();
      return;
    }

    if (_isPlaying) {
      _controller!.pause();
      setState(() {
        _isPlaying = false;
      });
    } else {
      if (_controller!.value.position >= _controller!.value.duration) {
        _controller!.seekTo(Duration.zero);
      }
      _controller!.play();
      setState(() {
        _isPlaying = true;
      });
    }
  }

  @override
  void dispose() {
    _controller?.removeListener(_videoListener);
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _togglePlayPause,
      child: SizedBox(
        width: 200,
        height: 200,
        child: ClipOval(
          child: Stack(
            alignment: Alignment.center,
            fit: StackFit.expand,
            children: [
              if (_isLoading ||
                  _hasError ||
                  _controller == null ||
                  !_controller!.value.isInitialized)
                (widget.highQualityUrl != null &&
                            widget.highQualityUrl!.isNotEmpty) ||
                        (widget.lowQualityBytes != null)
                    ? _ProgressiveNetworkImage(
                        url: widget.highQualityUrl ?? '',
                        previewBytes: widget.lowQualityBytes,
                        width: 200,
                        height: 200,
                        fit: BoxFit.cover,
                        keepAlive: false,
                      )
                    : Container(
                        color: Colors.black26,
                        child: const Center(
                          child: Icon(
                            Icons.video_library_outlined,
                            color: Colors.white,
                            size: 40,
                          ),
                        ),
                      )
              else
                VideoPlayer(_controller!),

              if (_isLoading)
                Container(
                  color: Colors.black.withOpacity(0.3),
                  child: const Center(
                    child: CircularProgressIndicator(
                      color: Colors.white,
                      strokeWidth: 2,
                    ),
                  ),
                ),

              if (!_isLoading &&
                  !_hasError &&
                  _controller != null &&
                  _controller!.value.isInitialized)
                AnimatedOpacity(
                  opacity: _isPlaying ? 0.0 : 0.8,
                  duration: const Duration(milliseconds: 200),
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.black.withOpacity(0.3),
                      shape: BoxShape.circle,
                    ),
                    child: Icon(
                      _isPlaying
                          ? Icons.pause_circle_filled
                          : Icons.play_circle_filled,
                      color: Colors.white,
                      size: 50,
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}
