import 'package:flutter/material.dart';
import '/data/models/message_model.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:provider/provider.dart';
import '/core/theme_manager.dart';
import 'dart:math';
import 'dart:io';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:open_filex/open_filex.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:video_player/video_player.dart';
import 'package:audioplayers/audioplayers.dart';

class MessageBubble extends StatefulWidget {
  final MessageModel message;
  final VoidCallback? onTap;
  final VoidCallback? onReplyTap;
  final VoidCallback? onImageTap;

  final Future<void>? Function(MessageModel)? onDownloadRequested;
  final Future<void>? Function(MessageModel)? onDownloadRequestedWithoutLoad;
  final Future<void>? Function(MessageModel)? onDownloadStoped;
  final bool autoLoadMedia;
  final ValueListenable<double?>? downloadProgress;

  const MessageBubble({
    super.key,
    required this.message,
    this.onTap,
    this.onReplyTap,
    this.onImageTap,
    this.onDownloadRequested,
    this.onDownloadRequestedWithoutLoad,
    this.onDownloadStoped,
    this.autoLoadMedia = true,
    this.downloadProgress,
  });

  @override
  State<MessageBubble> createState() => _MessageBubbleState();
}

class _MessageBubbleState extends State<MessageBubble> {
  bool _isMediaLoading = false;
  bool _requiresManualLoad = false;
  int _calculatedFileSize = 0;
  final int _autoDownloadLimit = 20 * 1024 * 1024; // 20 MB
  int minHeight = 0;
  int minWidth = 0;

  ValueListenable<double?>? _downloadProgressNotifier;
  Future<void>? _delayFuture;

  final MediaCacheManager _mediaCache = MediaCacheManager();

  bool get _isDownloading {
    return widget.message.localFile == null &&
        (_isMediaLoading || widget.downloadProgress?.value != null);
  }

  String get _messageKeyId =>
      (widget.message.id ?? widget.message.tempId ?? widget.message.hashCode)
          .toString();

  @override
  void initState() {
    super.initState();
    _syncDownloadProgressListener();
    _resolveFileSize();
    _generateVideoThumbnail();

    final isMedia =
        widget.message.messageType == MessageType.image ||
        widget.message.messageType == MessageType.video ||
        widget.message.messageType == MessageType.file ||
        widget.message.messageType == MessageType.videoNote ||
        widget.message.messageType == MessageType.voiceNote;

    if (isMedia) {
      final type = widget.message.messageType;
      final bool isNote =
          type == MessageType.voiceNote || type == MessageType.videoNote;

      if (isNote) {
        _requiresManualLoad = false;
      } else if (_calculatedFileSize > 0) {
        _requiresManualLoad =
            !widget.autoLoadMedia || _calculatedFileSize > _autoDownloadLimit;
      } else {
        _requiresManualLoad = true;
      }

      if (widget.message.localFile == null) {
        widget.onDownloadRequestedWithoutLoad?.call(widget.message);

        if (!_requiresManualLoad) {
          _checkAutoDownload();
        } else if (isNote) {
          _startDownload();
        }
      }
    }

    if (widget.message.messageType == MessageType.videoNote) {
      setState(() {
        minHeight = 160;
        minWidth = 160;
      });
    }
  }

  Future<ui.Image> getImageDimensions(File imageFile) async {
    final bytes = await imageFile.readAsBytes();
    final codec = await ui.instantiateImageCodec(bytes);
    final frameInfo = await codec.getNextFrame();
    return frameInfo.image;
  }

  Future<void> _generateVideoThumbnail() async {
    if (widget.message.messageType != MessageType.video) {
      return;
    }
    _delayFuture = Future.delayed(const Duration(milliseconds: 200)).then((
      _,
    ) async {
      if (!mounted) return;

      if (widget.message.messageType != MessageType.video ||
          widget.message.localFile == null) {
        return;
      }

      if (_mediaCache.getDimensions(_messageKeyId) != null &&
          _mediaCache.getThumbnailPath(_messageKeyId) != null) {
        return;
      }
      try {
        final double timestamp = DateTime.now().millisecondsSinceEpoch
            .toDouble();
        final targetDirectory = Directory(
          '${Directory.systemTemp.path}/thumbs/$timestamp',
        );

        if (!await targetDirectory.exists()) {
          await targetDirectory.create(recursive: true);
        }
        final String? thumb = await VideoThumbnail.thumbnailFile(
          video: widget.message.localFile!.path,
          thumbnailPath: targetDirectory.path,
          imageFormat: ImageFormat.JPEG,
          maxWidth: 400,
          quality: 75,
        );
        if (thumb == null) {
          debugPrint('Не удалось сгенерировать превью для видео');
          return;
        }
        File file = File(thumb);
        ui.Image img = await getImageDimensions(file);
        _mediaCache.saveDimensions(_messageKeyId, img.width, img.height);
        _mediaCache.saveThumbnailPath(_messageKeyId, thumb);
        if (mounted) {
          setState(() {
            minHeight = img.height;
            minWidth = img.width;
          });
        }
      } catch (e) {
        debugPrint('Ошибка генерации превью видео: $e');
      }
    });
  }

  @override
  void didUpdateWidget(covariant MessageBubble oldWidget) {
    super.didUpdateWidget(oldWidget);
    _syncDownloadProgressListener();

    print(
      "MessageBubble: didUpdateWidget called for message id ${widget.message.id}",
    );
    final bool fileExistsNow =
        widget.message.localFile != null &&
        widget.message.localFile!.existsSync();

    final bool _lastKnownFileExists =
        oldWidget.message.localFile != null &&
        oldWidget.message.localFile!.existsSync();

    // Флаг удаления: файл был (мы это помним), а сейчас его нет
    final bool becameNull = !fileExistsNow && _lastKnownFileExists;
    // Флаг появления: файла не было (мы это помнили), а сейчас он есть
    final bool becameReady = fileExistsNow && !_lastKnownFileExists;
    print(
      "ID ${widget.message.id}: fileExistsNow=$fileExistsNow, lastKnown=$_lastKnownFileExists, becameNull=$becameNull",
    );
    // Сценарий А: Файл есть в модели И физически существует на диске
    if (widget.message.localFile != null && fileExistsNow) {
      if (_requiresManualLoad || _isMediaLoading || becameReady) {
        setState(() {
          _requiresManualLoad = false;
          _isMediaLoading = false;
        });
      }

      if (becameReady) {
        _resolveFileSize();
        _generateVideoThumbnail();
      }
      return;
    }

    // Сценарий Б: Файл был удален физически с диска (или ссылка стала null)
    if (becameNull) {
      print("ОБНАРУЖЕНО УДАЛЕНИЕ ФАЙЛА ДЛЯ ${widget.message.id}");

      setState(() {
        _isMediaLoading = false;
        _requiresManualLoad = true;
      });

      _resolveFileSize();
      return;
    }

    if (fileExistsNow && !_lastKnownFileExists) {
      setState(() {
        _isMediaLoading = false;
      });
      _resolveFileSize();
    }

    // Сценарий В: Файла нет (и не было), проверяем изменение остальных параметров (текст, статус и т.д.)
    final bool fileChanged =
        widget.message.localFile != oldWidget.message.localFile;
    final bool sizeChanged =
        widget.message.fileSize != oldWidget.message.fileSize;
    final bool statusChanged =
        widget.message.status != oldWidget.message.status;
    final bool textChanged = widget.message.text != oldWidget.message.text;
    final bool fileIdChanged = widget.message.fileId != oldWidget.message.fileId;
    final bool fileNameChanged =
        widget.message.fileName != oldWidget.message.fileName;

    if (statusChanged || textChanged || fileChanged || fileIdChanged || fileNameChanged) {
      setState(() {});
    }
    if (fileChanged || sizeChanged || statusChanged || fileIdChanged || fileNameChanged) {
      _resolveFileSize();
    }

    // Ниже идет ваш стандартный код для автозагрузки медиа (isMedia && sizeChanged)...
    final isMedia =
        widget.message.messageType == MessageType.image ||
        widget.message.messageType == MessageType.video ||
        widget.message.messageType == MessageType.file ||
        widget.message.messageType == MessageType.videoNote ||
        widget.message.messageType == MessageType.voiceNote;

    if (isMedia && sizeChanged) {
      final oldSize = oldWidget.message.fileSize ?? 0;
      final newSize = widget.message.fileSize ?? 0;
      final type = widget.message.messageType;
      final bool isNote =
          type == MessageType.voiceNote || type == MessageType.videoNote;

      if (oldSize == 0 && newSize > 0) {
        setState(() {
          _requiresManualLoad = isNote
              ? false
              : !widget.autoLoadMedia || newSize > _autoDownloadLimit;
        });

        if (widget.message.localFile == null) {
          widget.onDownloadRequestedWithoutLoad?.call(widget.message);
        }
        if (!_requiresManualLoad && widget.message.localFile == null) {
          _startDownload();
          _generateVideoThumbnail();
        } else if (widget.message.localFile == null && isNote) {
          _startDownload();
        }
      }
    }
  }

  void _resolveFileSize() {
    if (widget.message.fileSize != null && widget.message.fileSize! > 0) {
      _calculatedFileSize = widget.message.fileSize!;
    } else if (widget.message.localFile != null &&
        widget.message.localFile!.existsSync()) {
      _calculatedFileSize = widget.message.localFile!.lengthSync();
    } else {
      _calculatedFileSize = 0;
    }
    if (mounted) setState(() {});
  }

  void _syncDownloadProgressListener() {
    if (_downloadProgressNotifier == widget.downloadProgress) return;
    _downloadProgressNotifier?.removeListener(_onDownloadProgressUpdated);
    _downloadProgressNotifier = widget.downloadProgress;
    _downloadProgressNotifier?.addListener(_onDownloadProgressUpdated);

    if (_downloadProgressNotifier?.value == null &&
        _isMediaLoading &&
        mounted) {
      setState(() {
        _isMediaLoading = false;
      });
    }
  }

  void _onDownloadProgressUpdated() {
    if (_downloadProgressNotifier?.value == null &&
        _isMediaLoading &&
        mounted) {
      setState(() {
        _isMediaLoading = false;
      });
    }
  }

  @override
  void dispose() {
    _downloadProgressNotifier?.removeListener(_onDownloadProgressUpdated);
    super.dispose();
  }

  Future<void> _handleDownload() async {
    if (widget.message.localFile != null) return;
    if (_isMediaLoading) return;
    if (!mounted) return;

    setState(() => _isMediaLoading = true);

    try {
      await widget.onDownloadRequested?.call(widget.message);
      if (widget.message.messageType == MessageType.image &&
          widget.message.localFile != null) {
        ui.Image img = await getImageDimensions(widget.message.localFile!);
        if (mounted) {
          setState(() {
            minHeight = img.height;
            minWidth = img.width;
          });
        }
      }
    } catch (e) {
      debugPrint('Download error: $e');
    } finally {
      if (mounted) {
        setState(() => _isMediaLoading = false);
      }
    }
  }

  Future<void> _handleStopDownload() async {
    if (!_isMediaLoading) return;

    if (mounted) {
      setState(() => _isMediaLoading = false);
    }
    try {
      await widget.onDownloadStoped?.call(widget.message);
    } catch (e) {
      debugPrint('Download stop error: $e');
    }
  }

  void _checkAutoDownload() {
    if (widget.message.localFile != null) return;
    _resolveFileSize();
    widget.onDownloadRequestedWithoutLoad?.call(widget.message);

    final type = widget.message.messageType;
    final isVoiceOrVideoNote =
        type == MessageType.voiceNote || type == MessageType.videoNote;

    if (_calculatedFileSize == 0 && !isVoiceOrVideoNote) {
      return;
    }

    if (widget.message.localFile == null && widget.message.fileId != null) {
      if (isVoiceOrVideoNote || _calculatedFileSize <= _autoDownloadLimit) {
        _startDownload();
      }
    }
  }

  Future<void> _startDownload() async {
    if (widget.message.localFile != null) {
      if (mounted) {
        setState(() {
          _isMediaLoading = false;
        });
      }
      return;
    }
    if (_isMediaLoading || widget.message.localFile != null) return;
    setState(() => _isMediaLoading = true);
    try {
      await widget.onDownloadRequested?.call(widget.message);
    } catch (e) {
      debugPrint("Ошибка при скачивании медиа: $e");
    } finally {
      if (mounted) {
        setState(() {
          _isMediaLoading = false;
        });
      }
    }
  }

  void _openFile() async {
    if (widget.message.localFile != null) {
      debugPrint("Открываем файл: ${widget.message.localFile!.path}");
      final directory = await getApplicationDocumentsDirectory();

      final decPath = '${directory.path}/${widget.message.fileName ?? 'file'}';
      widget.message.localFile!
          .copy(decPath)
          .then((copiedFile) {
            OpenFilex.open(copiedFile.path)
                .then((result) {
                  debugPrint("Результат открытия файла: ${result.type}");
                  if (result.type != ResultType.done) {
                    debugPrint("Ошибка при открытии файла: ${result.message}");
                  }
                })
                .catchError((e) {
                  debugPrint("Ошибка при открытии файла: $e");
                });
          })
          .catchError((e) {
            debugPrint("Ошибка при копировании файла для открытия: $e");
          });
    }
  }

  bool get _isDisplayableFileReady {
    return widget.message.localFile != null &&
        widget.message.localFile!.existsSync();
  }

  @override
  Widget build(BuildContext context) {
    final isMe = widget.message.isMe;
    final primaryTextColor = Colors.white;
    final secondaryTextColor = Colors.white70;
    final linkColor = const Color(0xFF81D4FA);
    final bool canShowMedia = _isDisplayableFileReady;

    return Align(
      alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: widget.onTap,
          onLongPress: widget.onTap,
          borderRadius: BorderRadius.only(
            topLeft: const Radius.circular(16),
            topRight: const Radius.circular(16),
            bottomLeft: Radius.circular(isMe ? 16 : 0),
            bottomRight: Radius.circular(isMe ? 0 : 16),
          ),
          child: Container(
            margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
            padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
            constraints: BoxConstraints(
              maxWidth: MediaQuery.of(context).size.width * 0.7,
            ),
            decoration: BoxDecoration(
              color: widget.message.messageType != MessageType.videoNote
                  ? isMe
                        ? Theme.of(context).colorScheme.brightness ==
                                  Brightness.dark
                              ? Theme.of(context).colorScheme.primaryContainer
                              : Theme.of(context).colorScheme.primary
                        : Colors.grey[800]
                  : Colors.transparent,
              borderRadius: BorderRadius.only(
                topLeft: const Radius.circular(16),
                topRight: const Radius.circular(16),
                bottomLeft: Radius.circular(isMe ? 16 : 0),
                bottomRight: Radius.circular(isMe ? 0 : 16),
              ),
            ),
            child: IntrinsicWidth(
              child: Column(
                crossAxisAlignment: isMe
                    ? CrossAxisAlignment.end
                    : CrossAxisAlignment.start,
                children: [
                  if (widget.message.replyToText != null) ...[
                    _buildReplyWidget(isMe, secondaryTextColor),
                  ],
                  Align(
                    alignment: isMe
                        ? Alignment.centerRight
                        : Alignment.centerLeft,
                    child: _buildMessageBody(
                      primaryTextColor,
                      secondaryTextColor,
                    ),
                  ),
                  if (widget.message.messageType == MessageType.text ||
                      widget.message.text.isNotEmpty) ...[
                    const SizedBox(height: 4),
                    Align(
                      alignment: Alignment.centerLeft,
                      child: Linkify(
                        onOpen: (link) async {
                          final Uri url = Uri.parse(link.url);
                          if (!await launchUrl(
                            url,
                            mode: LaunchMode.externalApplication,
                          )) {
                            throw Exception('Could not launch $url');
                          }
                        },
                        text: widget.message.text,
                        style: TextStyle(color: primaryTextColor),
                        linkStyle: TextStyle(
                          color: linkColor,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ],
                  const SizedBox(height: 4),
                  _buildTimeAndStatusRow(isMe, secondaryTextColor),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildMessageBody(Color primaryColor, Color secondaryColor) {
    switch (widget.message.messageType) {
      case MessageType.image:
        return _buildImagePreview(primaryColor, secondaryColor);
      case MessageType.video:
        return _buildVideoPreview(primaryColor, secondaryColor);
      case MessageType.file:
        return _buildFileBubble(primaryColor, secondaryColor);
      case MessageType.videoNote:
        return _buildVideoNotePreview(primaryColor, secondaryColor);
      case MessageType.voiceNote:
        return _buildVoiceNoteBubble(primaryColor, secondaryColor);
      default:
        return const SizedBox.shrink();
    }
  }

  Widget _buildImagePreview(Color textCol, Color subTextCol) {
    _resolveFileSize();
    final bool isDownloaded = widget.message.localFile != null;
    final bool isSending = widget.message.status == MessageStatus.sending;
    final bool isEncrypting = widget.message.status == MessageStatus.encrypting;
    final isTooLarge = _calculatedFileSize > _autoDownloadLimit;
    final displaySize = formatBytes(_calculatedFileSize, 1);

    final double screenMaxWidth = MediaQuery.of(context).size.width * 0.6;
    final double screenMaxHeight = MediaQuery.of(context).size.height * 0.4;

    final double calculatedMinWidth = minWidth > 0
        ? math.min(minWidth.toDouble(), screenMaxWidth)
        : MediaQuery.of(context).size.width * 0.4;

    final double calculatedMinHeight = minHeight > 0
        ? math.min(minHeight.toDouble(), screenMaxHeight)
        : MediaQuery.of(context).size.height * 0.25;

    return GestureDetector(
      onTap: (_isDownloading || isSending || isEncrypting || !isDownloaded)
          ? () {
              if (_isMediaLoading || isSending || isEncrypting) {
                _handleStopDownload();
              } else {
                if (isDownloaded) {
                  widget.onImageTap?.call();
                } else if (!isSending && !isEncrypting) {
                  _handleDownload();
                }
              }
            }
          : widget.onImageTap,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(12),
        child: Container(
          color: Colors.black.withOpacity(0.05),
          constraints: BoxConstraints(
            minWidth: calculatedMinWidth,
            maxWidth: screenMaxWidth,
            minHeight: calculatedMinHeight,
            maxHeight: screenMaxHeight,
          ),
          child: Stack(
            fit: StackFit.loose,
            alignment: Alignment.centerRight,
            children: [
              if (isDownloaded)
                Image.file(widget.message.localFile!, fit: BoxFit.cover)
              else
                _buildMediaPlaceholder(
                  Icons.image,
                  "Фото",
                  isTooLarge,
                  textCol,
                  subTextCol,
                ),
              if (!isDownloaded &&
                  !_isDownloading &&
                  !isSending &&
                  !isEncrypting &&
                  _calculatedFileSize > 0)
                Positioned(
                  bottom: 8,
                  left: 8,
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 6,
                      vertical: 3,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.black.withOpacity(0.55),
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        const Icon(
                          Icons.arrow_downward_rounded,
                          color: Colors.white,
                          size: 12,
                        ),
                        const SizedBox(width: 3),
                        Text(
                          displaySize,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 10,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              if (_isMediaLoading || isSending || isEncrypting)
                Positioned.fill(
                  child: _buildProgressOverlay(
                    (isSending || isEncrypting),
                    isEncrypting,
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildVideoPreview(Color textCol, Color subTextCol) {
    final bool isDownloaded = widget.message.localFile != null;
    final bool isSending = widget.message.status == MessageStatus.sending;
    final bool isEncrypting = widget.message.status == MessageStatus.encrypting;
    final isTooLarge = _calculatedFileSize > _autoDownloadLimit;
    final displaySize = formatBytes(_calculatedFileSize, 1);
    final cachedSize = _mediaCache.getDimensions(_messageKeyId);
    final cachedThumbPath = _mediaCache.getThumbnailPath(_messageKeyId);

    double finalWidth = 240.0;
    double finalHeight = 160.0;

    if (cachedSize != null && cachedSize.width > 0 && cachedSize.height > 0) {
      final double aspectRatio = cachedSize.width / cachedSize.height;
      finalWidth = 250.0;
      finalHeight = finalWidth / aspectRatio;
      final double maxAllowedHeight = MediaQuery.of(context).size.height * 0.4;
      final double minAllowedHeight = MediaQuery.of(context).size.height * 0.15;
      finalHeight = finalHeight.clamp(minAllowedHeight, maxAllowedHeight);
    }

    return GestureDetector(
      onTap: () {
        if (_isMediaLoading || isSending || isEncrypting) {
          _handleStopDownload();
        } else {
          if (isDownloaded) {
            widget.onImageTap?.call();
          } else if (!_isDisplayableFileReady && !isSending && !isEncrypting) {
            _handleDownload();
          }
        }
      },
      child: Padding(
        padding: const EdgeInsets.only(bottom: 4),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(12),
          child: Container(
            width: finalWidth,
            height: finalHeight,
            color: Colors.black.withOpacity(0.1),
            child: Stack(
              alignment: Alignment.center,
              fit: StackFit.expand,
              children: [
                if (isDownloaded && !isSending && !isEncrypting) ...[
                  if (cachedThumbPath != null)
                    Image.file(File(cachedThumbPath), fit: BoxFit.cover)
                  else
                    const SizedBox(
                      child: Center(
                        child: CircularProgressIndicator(color: Colors.white),
                      ),
                    ),
                  Icon(
                    Icons.play_circle_fill,
                    color: Colors.white.withOpacity(0.9),
                    size: 50,
                  ),
                ] else ...[
                  if (!_isDisplayableFileReady &&
                      (isSending || isEncrypting) &&
                      widget.message.localFile != null &&
                      cachedThumbPath != null)
                    Image.file(File(cachedThumbPath), fit: BoxFit.cover)
                  else
                    _buildMediaPlaceholder(
                      Icons.videocam,
                      "Video",
                      isTooLarge,
                      textCol,
                      subTextCol,
                    ),
                ],
                if (!isDownloaded &&
                    !_isDownloading &&
                    !isSending &&
                    !isEncrypting &&
                    _calculatedFileSize > 0)
                  Positioned(
                    bottom: 8,
                    left: 8,
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 6,
                        vertical: 3,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.black.withOpacity(0.55),
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          const Icon(
                            Icons.arrow_downward_rounded,
                            color: Colors.white,
                            size: 12,
                          ),
                          const SizedBox(width: 3),
                          Text(
                            displaySize,
                            style: const TextStyle(
                              color: Colors.white,
                              fontSize: 10,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                if (_isDownloading || isSending || isEncrypting)
                  Positioned.fill(
                    child: _buildProgressOverlay(
                      (isSending || isEncrypting),
                      isEncrypting,
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildMediaPlaceholder(
    IconData icon,
    String typeLabel,
    bool isTooLarge,
    Color textCol,
    Color subTextCol,
  ) {
    _resolveFileSize();
    final displaySize = formatBytes(_calculatedFileSize, 1);
    final sizeString = _calculatedFileSize > 0
        ? " ($displaySize)"
        : " (Загрузка размера...)";

    if (_isMediaLoading) return const SizedBox.shrink();
    return Container(
      color: Colors.black.withOpacity(0.05),
      padding: const EdgeInsets.all(12),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            isTooLarge ? Icons.download_for_offline : icon,
            size: 42,
            color: subTextCol,
          ),
          const SizedBox(height: 6),
          Text(
            isTooLarge
                ? "Файл слишком большой$sizeString"
                : "$typeLabel$sizeString",
            textAlign: TextAlign.center,
            style: TextStyle(
              fontSize: 12,
              color: textCol,
              fontWeight: FontWeight.w500,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            "Нажмите для загрузки",
            style: TextStyle(
              fontSize: 10,
              color: isTooLarge ? Colors.black54 : subTextCol,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildProgressOverlay(bool isSending, bool isEncrypting) {
    return GestureDetector(
      onTap: () async => await _handleStopDownload(),
      child: Container(
        color: Colors.black.withOpacity(0.5),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              if (isSending) ...[
                widget.downloadProgress != null
                    ? ValueListenableBuilder<double?>(
                        valueListenable: widget.downloadProgress!,
                        builder: (context, value, _) {
                          final double currentProgress = value ?? 0.0;
                          return TweenAnimationBuilder<double>(
                            tween: Tween(begin: 0.0, end: currentProgress),
                            duration: const Duration(milliseconds: 150),
                            builder: (context, val, _) {
                              return _buildCircularIndicator(
                                val,
                                isEncrypting ? "Шифрование" : "Отправка",
                                false,
                                _calculatedFileSize,
                              );
                            },
                          );
                        },
                      )
                    : TweenAnimationBuilder<double>(
                        tween: Tween(begin: 0.0, end: 0.0),
                        duration: const Duration(milliseconds: 150),
                        builder: (context, val, _) {
                          return _buildCircularIndicator(
                            val,
                            isEncrypting ? "Шифрование" : "Отправка",
                            false,
                            _calculatedFileSize,
                          );
                        },
                      ),
              ] else ...[
                widget.downloadProgress != null
                    ? (widget.message.localFile == null
                          ? ValueListenableBuilder<double?>(
                              valueListenable: widget.downloadProgress!,
                              builder: (context, value, _) {
                                final bool isIndeterminate = value == null;
                                return TweenAnimationBuilder<double>(
                                  tween: Tween(begin: 0.0, end: value ?? 0.0),
                                  duration: const Duration(milliseconds: 150),
                                  builder: (context, val, _) {
                                    return _buildCircularIndicator(
                                      val,
                                      "Загрузка",
                                      isIndeterminate,
                                      _calculatedFileSize,
                                    );
                                  },
                                );
                              },
                            )
                          : const SizedBox())
                    : const CircularProgressIndicator(color: Colors.white),
              ],
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildCircularIndicator(
    double val,
    String label,
    bool isIndeterminate,
    int totalBytes,
  ) {
    final currentBytes = (totalBytes * val).toInt();
    final String progressText = totalBytes > 0
        ? "${formatBytes(currentBytes, 1)} / ${formatBytes(totalBytes, 1)}"
        : "Передача...";
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Stack(
          alignment: Alignment.center,
          children: [
            SizedBox(
              width: 60,
              height: 60,
              child: CircularProgressIndicator(
                value: isIndeterminate ? null : val,
                strokeWidth: 4,
                color: Colors.white,
                backgroundColor: Colors.white24,
              ),
            ),
            Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                if (!isIndeterminate)
                  Text(
                    "${(val * 100).toInt()}%",
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                Text(
                  label,
                  style: TextStyle(
                    color: Colors.white60,
                    fontSize: isIndeterminate ? 10 : 8,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ],
        ),
        const SizedBox(height: 10),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
          decoration: BoxDecoration(
            color: Colors.black38,
            borderRadius: BorderRadius.circular(6),
          ),
          child: Text(
            progressText,
            style: const TextStyle(
              color: Colors.white,
              fontSize: 10,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildFileBubble(Color textCol, Color subTextCol) {
    _resolveFileSize();
    final bool isDownloaded = widget.message.localFile != null;
    final bool isSending = widget.message.status == MessageStatus.sending;
    final bool isEncrypting = widget.message.status == MessageStatus.encrypting;
    final status = isEncrypting
        ? 'Шифрование'
        : isSending
        ? 'Отправка'
        : _isMediaLoading
        ? 'Загрузка'
        : '';
    final displaySize = formatBytes(_calculatedFileSize, 1);
    return GestureDetector(
      onTap: () {
        if (widget.message.localFile != null) {
          _openFile();
        } else {
          _handleDownload();
        }
      },
      child: Container(
        padding: const EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: Colors.black.withOpacity(0.04),
          borderRadius: BorderRadius.circular(12),
        ),
        constraints: BoxConstraints(
          minWidth: MediaQuery.of(context).size.width * 0.6,
          maxWidth: MediaQuery.of(context).size.width * 0.6,
        ),
        child: Row(
          children: [
            Stack(
              alignment: Alignment.center,
              children: [
                Icon(Icons.insert_drive_file, size: 38, color: subTextCol),
                if (!_isMediaLoading &&
                    !isDownloaded &&
                    !isSending &&
                    !isEncrypting)
                  const Icon(
                    Icons.download_rounded,
                    size: 16,
                    color: Colors.white70,
                  ),
              ],
            ),
            const SizedBox(width: 10),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    widget.message.fileName ?? 'Файл',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 13,
                      color: textCol,
                    ),
                  ),
                  Row(
                    children: [
                      Flexible(
                        child: Text(
                          displaySize,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          style: TextStyle(fontSize: 11, color: subTextCol),
                        ),
                      ),
                      const SizedBox(width: 6),
                      Flexible(
                        child: Align(
                          alignment: Alignment.centerRight,
                          child: Text(
                            status,
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(fontSize: 11, color: subTextCol),
                          ),
                        ),
                      ),
                    ],
                  ),
                  if (_isMediaLoading || isSending || isEncrypting)
                    ValueListenableBuilder<double?>(
                      valueListenable:
                          widget.downloadProgress ??
                          ValueNotifier<double?>(0.0),
                      builder: (context, value, _) {
                        // Если value null, принудительно показываем indeterminate (бесконечную полоску)
                        // или 0, чтобы пользователь видел, что что-то происходит
                        final progress = value ?? 0.0;

                        return Padding(
                          padding: const EdgeInsets.only(top: 6),
                          child: LinearProgressIndicator(
                            value: progress > 0
                                ? progress
                                : null, // null = бесконечная анимация, если прогресс еще не пришел
                            minHeight: 3,
                            backgroundColor: Colors.white24,
                            color: Colors.white70,
                          ),
                        );
                      },
                    ),
                ],
              ),
            ),
            if (!isDownloaded &&
                !_isMediaLoading &&
                !isSending &&
                !isEncrypting)
              IconButton(
                icon: const Icon(Icons.download_rounded, color: Colors.white70),
                onPressed: _handleDownload,
              ),
            if (!isDownloaded && _isMediaLoading && !isSending && !isEncrypting)
              IconButton(
                icon: const Icon(Icons.cancel, color: Colors.white70),
                onPressed: _handleStopDownload,
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildReplyWidget(bool isMe, Color subTextCol) {
    final isMedia =
        widget.message.messageType == MessageType.image ||
        widget.message.messageType == MessageType.video ||
        widget.message.messageType == MessageType.videoNote;
    return GestureDetector(
      onTap: widget.onReplyTap,
      behavior: HitTestBehavior.opaque,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        margin: const EdgeInsets.only(bottom: 6),
        decoration: BoxDecoration(
          color: (isMedia
              ? Colors.white24.withOpacity(0.5)
              : isMe
              ? Colors.white.withOpacity(0.1)
              : Colors.black.withOpacity(0.1)),
          borderRadius: BorderRadius.circular(8),
          border: Border(left: BorderSide(color: subTextCol, width: 2)),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.reply, size: 14, color: subTextCol),
            const SizedBox(width: 4),
            Expanded(
              child: Text(
                widget.message.replyToText!,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  color: subTextCol,
                  fontSize: 12,
                  fontStyle: FontStyle.italic,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildVideoNotePreview(
    Color primaryTextColor,
    Color secondaryTextColor,
  ) {
    final file = widget.message.localFile;
    final path = file?.path ?? "";
    final id = widget.message.id ?? widget.message.tempId ?? "no_id";
    final bool isDownloaded = file != null;
    final bool isSending = widget.message.status == MessageStatus.sending;
    final bool isEncrypting = widget.message.status == MessageStatus.encrypting;

    debugPrint(
      '==> BUBBLE_VIDEO_RENDER: msgId=$id, status=${widget.message.status}, hasFile=${file != null}, path=$path',
    );

    return ValueListenableBuilder<String?>(
      valueListenable: InlineVideoNotePlayer.activeVideoPathNotifier,
      builder: (context, activePath, _) {
        final bool isActive = activePath == path;
        final double size = isActive ? 260 : 160;

        return GestureDetector(
          onTap: (_isDownloading || isSending || isEncrypting || !isDownloaded)
              ? () {
                  if (_isDownloading || isSending || isEncrypting) {
                    _handleStopDownload();
                  } else {
                    if (!isDownloaded) {
                      _handleDownload();
                    }
                  }
                }
              : widget.onTap,
          child: Stack(
            alignment: Alignment.center,
            children: [
              // Плавное изменение размеров кружка при проигрывании
              AnimatedContainer(
                duration: const Duration(milliseconds: 250),
                curve: Curves.fastOutSlowIn,
                width: size,
                height: size,
                decoration: const BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.black12,
                ),
                child: ClipOval(
                  child: isDownloaded
                      ? InlineVideoNotePlayer(videoPath: path)
                      : Container(
                          color: Colors.black54,
                          child: Icon(
                            Icons.play_arrow_rounded,
                            size: 48,
                            color: primaryTextColor,
                          ),
                        ),
                ),
              ),

              // Защита: Кнопка скачать поверх кружка выводится ТОЛЬКО если файла нет физически на диске
              if (!isDownloaded &&
                  !_isDownloading &&
                  !isSending &&
                  !isEncrypting)
                ClipOval(
                  child: Container(
                    width: size,
                    height: size,
                    color: Colors.black.withOpacity(0.4),
                    child: const Icon(
                      Icons.arrow_downward_rounded,
                      color: Colors.white,
                      size: 36,
                    ),
                  ),
                ),

              if (_isDownloading || isSending || isEncrypting)
                SizedBox(
                  width: size,
                  height: size,
                  child: _buildProgressOverlay(
                    (isSending || isEncrypting),
                    isEncrypting,
                  ),
                ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildVoiceNoteBubble(Color textCol, Color subTextCol) {
    final String path = widget.message.localFile?.path ?? '';
    final bool isDownloaded = path.isNotEmpty && File(path).existsSync();

    // Определяем статусы отправки на основе твоей модели данных MessageStatus
    final bool isSendingNow = widget.message.status == MessageStatus.sending;

    // Если файл еще НЕ скачан локально, показываем заглушку загрузки
    if (!isDownloaded) {
      return Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        constraints: BoxConstraints(
          minWidth: MediaQuery.of(context).size.width * 0.6,
          maxWidth: MediaQuery.of(context).size.width * 0.6,
        ),
        child: Row(
          children: [
            // Индикатор процесса или стрелочка скачивания
            SizedBox(
              width: 28,
              height: 28,
              child: _isMediaLoading
                  ? CircularProgressIndicator(
                      valueColor: AlwaysStoppedAnimation<Color>(textCol),
                      strokeWidth: 2.5,
                    )
                  : Icon(Icons.download_rounded, color: textCol, size: 28),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    "Голосовое сообщение",
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      fontSize: 14,
                      color: textCol,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 2),
                  Text(
                    _isMediaLoading ? "Загрузка..." : "Нажмите для скачивания",
                    style: TextStyle(fontSize: 11, color: subTextCol),
                  ),
                ],
              ),
            ),
          ],
        ),
      );
    }

    // Если файл СКАЧАН, отдаем управление полноценному интерактивному плееру!
    return Container(
      padding: const EdgeInsets.all(4),
      constraints: BoxConstraints(
        minWidth: MediaQuery.of(context).size.width * 0.65,
        maxWidth: MediaQuery.of(context).size.width * 0.65,
      ),
      child: Opacity(
        opacity: isSendingNow ? 0.5 : 1.0,
        child: AbsorbPointer(
          // Блокируем клики только во время непосредственной отправки сообщения
          absorbing: isSendingNow,
          child: InlineVoiceNotePlayer(
            key: ValueKey(
              'voice_note_${widget.message.fileId ?? widget.message.tempId}',
            ),
            audioPath: path,
          ),
        ),
      ),
    );
  }

  Widget _buildTimeAndStatusRow(bool isMe, Color secondaryTextColor) {
    final timeStr =
        "${widget.message.createdAt.hour.toString().padLeft(2, '0')}:${widget.message.createdAt.minute.toString().padLeft(2, '0')}";
    return Row(
      mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          timeStr,
          style: TextStyle(color: secondaryTextColor, fontSize: 10),
        ),
        if (widget.message.editedAt != null)
          Text(
            " (изменено)",
            style: TextStyle(color: secondaryTextColor, fontSize: 10, fontStyle: FontStyle.italic),
          ),
        if (isMe) ...[
          const SizedBox(width: 4),
          Icon(
            widget.message.status == MessageStatus.read
                ? Icons.done_all
                : Icons.done,
            color: secondaryTextColor,
            size: 14,
          ),
        ],
      ],
    );
  }

  String formatBytes(int bytes, int decimals) {
    if (bytes <= 0) return "0 B";
    const suffixes = ["B", "KB", "MB", "GB", "TB"];
    var i = (log(bytes) / log(1024)).floor();
    return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) +
        " " +
        suffixes[i];
  }
}

class MediaCacheManager {
  static final MediaCacheManager _instance = MediaCacheManager._internal();
  factory MediaCacheManager() => _instance;
  MediaCacheManager._internal();

  final Map<String, ui.Size> _dimensionsCache = {};
  final Map<String, String> _thumbnailPathCache = {};

  void saveDimensions(String id, int width, int height) {
    _dimensionsCache[id] = ui.Size(width.toDouble(), height.toDouble());
  }

  ui.Size? getDimensions(String id) => _dimensionsCache[id];

  void saveThumbnailPath(String id, String path) {
    _thumbnailPathCache[id] = path;
  }

  String? getThumbnailPath(String id) => _thumbnailPathCache[id];
}

// ==========================================
// ВСТРОЕННЫЙ ПЛЕЕР ДЛЯ ВИДЕО-КРУЖКОВ
// ==========================================
class InlineVideoNotePlayer extends StatefulWidget {
  final String videoPath;
  const InlineVideoNotePlayer({super.key, required this.videoPath});

  static final ValueNotifier<String?> activeVideoPathNotifier =
      ValueNotifier<String?>(null);

  @override
  State<InlineVideoNotePlayer> createState() => _InlineVideoNotePlayerState();
}

class _InlineVideoNotePlayerState extends State<InlineVideoNotePlayer> {
  VideoPlayerController? _controller;
  bool _isExpanded = false;
  Future<void>? _delayFuture;
  String? _initError;
  bool _isInitializing = false;
  bool _wasPlaying = false;

  @override
  void initState() {
    super.initState();
    debugPrint('  --> PLAYER_STATE: initState() для пути: ${widget.videoPath}');
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _initVideoWithDelay();
    });
    InlineVideoNotePlayer.activeVideoPathNotifier.addListener(
      _onActiveVideoChanged,
    );
  }

  void _initVideoWithDelay() async {
    if (widget.videoPath.isEmpty) return;
    await Future.delayed(const Duration(milliseconds: 200));

    // Извлекаем msgId из пути или передаем из MessageBubble.
    // Если у тебя в InlineVideoNotePlayer есть доступ к msgId, лучше использовать его.
    // Для примера вытащим цифры из хэша файла или сделаем случайный сдвиг:
    final int stableSalt =
        widget.videoPath.hashCode % 6; // Получим число от 0 до 5

    // Каждое видео получит свой уникальный сдвиг: 0мс, 150мс, 300мс, 450мс и т.д.
    final int delayMs = 150 * stableSalt;

    debugPrint(
      '--> PLAYER_STATE: Планируем запуск плеера с задержкой $delayMs мс для ${widget.videoPath.split('/').last}',
    );

    _delayFuture = Future.delayed(Duration(milliseconds: delayMs)).then((
      _,
    ) async {
      // КРИТИЧЕСКИ ВАЖНО: проверяем, жив ли еще виджет на экране после паузы
      if (!mounted) return;
      _initVideo();
    });
  }

  @override
  void dispose() {
    debugPrint(
      '  --> PLAYER_STATE: dispose() вызыван для пути: ${widget.videoPath}',
    );

    InlineVideoNotePlayer.activeVideoPathNotifier.removeListener(
      _onActiveVideoChanged,
    );
    _controller?.removeListener(_videoListener);
    _controller?.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant InlineVideoNotePlayer oldWidget) {
    super.didUpdateWidget(oldWidget);

    debugPrint(
      '  --> PLAYER_STATE: didUpdateWidget. Старый путь="${oldWidget.videoPath}", Новый путь="${widget.videoPath}"',
    );

    // 1. Если новый путь пустой, НЕ уничтожаем старый рабочий контроллер!
    // Скорее всего, это временный лаг обновления состояния в ListView.
    if (widget.videoPath.isEmpty && oldWidget.videoPath.isNotEmpty) {
      debugPrint(
        '  --> PLAYER_STATE: Новый путь пустой. Игнорируем сброс плеера.',
      );
      return;
    }

    // 2. Если пути формально отличаются, но плеер уже инициализирован и успешно играет,
    // а разница лишь в динамическом префиксе дешифрованного файла (одно и то же видео)
    if (oldWidget.videoPath != widget.videoPath) {
      // Защита: Если старый файл существовал и новый существует, и они имеют одинаковый размер
      // (или мы просто доверяем текущему плееру), не нужно дергать нативный контроллер.
      if (_controller != null && _controller!.value.isInitialized) {
        final oldFile = File(oldWidget.videoPath);
        final newFile = File(widget.videoPath);

        if (oldFile.existsSync() &&
            newFile.existsSync() &&
            oldFile.lengthSync() == newFile.lengthSync()) {
          debugPrint(
            '  --> PLAYER_STATE: Пути разные, но файлы идентичны по размеру. Не пересоздаем.',
          );
          return;
        }
      }

      debugPrint(
        '  --> PLAYER_STATE: Путь изменился на валидный! Пересоздаем контроллер.',
      );
      if (_controller != null) {
        _controller!.removeListener(_videoListener);
        final oldController = _controller!;
        _controller = null;
        oldController.dispose().catchError(
          (e) => debugPrint('Error disposing vc: $e'),
        );
      }
      _initVideo();
    }
  }

  void _initVideo() {
    if (widget.videoPath.isEmpty) {
      debugPrint('  --> PLAYER_INIT: Отмена инициализации. Путь пустой.');
      return;
    }

    final file = File(widget.videoPath);
    if (!file.existsSync()) {
      debugPrint(
        '  --> PLAYER_INIT: Отмена инициализации. Файла физически НЕТ на диске по пути: ${widget.videoPath}',
      );
      return;
    }

    debugPrint(
      '  --> PLAYER_INIT: Начинаем VideoPlayerController.file(). Исходный файл существует.',
    );

    if (_isInitializing) return;
    _isInitializing = true;
    _initError = null;

    _controller = VideoPlayerController.file(file)
      ..initialize()
          .then((_) {
            debugPrint(
              '  --> PLAYER_INIT: СУПЕР! Контроллер успешно инициализирован для ${widget.videoPath}',
            );
            _isInitializing = false;
            _initError = null;
            if (mounted) setState(() {});
          })
          .catchError((e) {
            _isInitializing = false;
            _initError = e.toString();
            final oldController = _controller;
            _controller = null;
            oldController?.removeListener(_videoListener);
            oldController?.dispose().catchError((_) {});
            if (mounted) setState(() {});
            debugPrint(
              '  --> PLAYER_INIT_ERROR: Фатальный сбой VideoPlayer: $e',
            );
          });

    _controller?.addListener(_videoListener);
  }

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

    final isPlaying = _controller!.value.isPlaying;
    final isInitialized = _controller!.value.isInitialized;

    if (isInitialized && _wasPlaying && !isPlaying) {
      _isExpanded = false;

      if (InlineVideoNotePlayer.activeVideoPathNotifier.value ==
          widget.videoPath) {
        InlineVideoNotePlayer.activeVideoPathNotifier.value = null;
      }
    }

    _wasPlaying = isPlaying;

    setState(() {});
  }

  void _onActiveVideoChanged() {
    if (!mounted || _controller == null) return;
    final activePath = InlineVideoNotePlayer.activeVideoPathNotifier.value;

    if (activePath != widget.videoPath) {
      setState(() {
        if (_controller!.value.isPlaying) {
          _controller!.pause();
        }
        _isExpanded = false;
      });
    }
  }

  void _togglePlay() {
    if (_controller == null || !_controller!.value.isInitialized) return;

    setState(() {
      if (_controller!.value.isPlaying) {
        _controller!.pause();
        _isExpanded = false;
        if (InlineVideoNotePlayer.activeVideoPathNotifier.value ==
            widget.videoPath) {
          InlineVideoNotePlayer.activeVideoPathNotifier.value = null;
        }
      } else {
        InlineVideoNotePlayer.activeVideoPathNotifier.value = widget.videoPath;
        _controller!.play();
        _controller!.setLooping(true);
        _isExpanded = true;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    final double size = _isExpanded ? 260.0 : 160.0;
    final bool isInitialized =
        _controller != null && _controller!.value.isInitialized;
    final bool hasInitError = _initError != null;

    double progress = 0.0;
    if (isInitialized) {
      final duration = _controller!.value.duration.inMilliseconds;
      final position = _controller!.value.position.inMilliseconds;
      progress = duration > 0 ? (position / duration) : 0.0;
    }

    return AnimatedContainer(
      duration: const Duration(milliseconds: 180),
      curve: Curves.easeOut,
      width: size,
      height: size,
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        color: Colors
            .black12, // Даем легкую подложку вместо прозрачности, чтобы круг было видно
      ),
      child: ClipOval(
        child: Stack(
          alignment: Alignment.center,
          children: [
            if (hasInitError)
              _InlineVideoInitErrorFallback(videoPath: widget.videoPath)
            else if (isInitialized)
              GestureDetector(
                onTap: _togglePlay,
                child: SizedBox(
                  width: size,
                  height: size,
                  child: FittedBox(
                    fit: BoxFit.cover,
                    child: SizedBox(
                      width: _controller!.value.size.width,
                      height: _controller!.value.size.height,
                      child: VideoPlayer(_controller!),
                    ),
                  ),
                ),
              )
            else
              // Красивый лоадер, пока файл скачивается или обрабатывается
              const Center(
                child: SizedBox(
                  width: 30,
                  height: 30,
                  child: CircularProgressIndicator(
                    strokeWidth: 2,
                    valueColor: AlwaysStoppedAnimation<Color>(Colors.white70),
                  ),
                ),
              ),

            if (isInitialized && !hasInitError)
              Positioned.fill(
                child: IgnorePointer(
                  child: CustomPaint(
                    painter: CircleProgressPainter(
                      progress: progress,
                      progressColor: Colors.white,
                      backgroundColor: Colors.white30,
                      strokeWidth: 4.0,
                    ),
                  ),
                ),
              ),

            if (isInitialized && !_controller!.value.isPlaying && !hasInitError)
              IgnorePointer(
                child: Container(
                  color: Colors.black26,
                  alignment: Alignment.center,
                  child: const Icon(
                    Icons.play_arrow,
                    size: 40,
                    color: Colors.white,
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

class _InlineVideoInitErrorFallback extends StatelessWidget {
  final String videoPath;

  const _InlineVideoInitErrorFallback({required this.videoPath});

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.black12,
      child: InkWell(
        onTap: () async {
          try {
            await OpenFilex.open(videoPath);
          } catch (e) {
            debugPrint('  --> PLAYER_FALLBACK_ERROR: $e');
          }
        },
        child: const Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(Icons.play_disabled, color: Colors.white70, size: 40),
              SizedBox(height: 8),
              Text(
                'Видео не воспроизводится\n Нажмите, чтобы открыть внешним плеером',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.white70, fontSize: 12),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class CircleProgressPainter extends CustomPainter {
  final double progress;
  final Color progressColor;
  final Color backgroundColor;
  final double strokeWidth;

  CircleProgressPainter({
    required this.progress,
    required this.progressColor,
    required this.backgroundColor,
    required this.strokeWidth,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final Offset center = Offset(size.width / 2, size.height / 2);
    final double radius = (size.width - strokeWidth) / 2;

    final Paint backgroundPaint = Paint()
      ..color = backgroundColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth;

    canvas.drawCircle(center, radius, backgroundPaint);

    final Paint progressPaint = Paint()
      ..color = progressColor
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = strokeWidth;

    double startAngle = -math.pi / 2;
    double sweepAngle = 2 * math.pi * progress;

    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      startAngle,
      sweepAngle,
      false,
      progressPaint,
    );
  }

  @override
  bool shouldRepaint(covariant CircleProgressPainter oldDelegate) {
    return oldDelegate.progress != progress ||
        oldDelegate.progressColor != progressColor ||
        oldDelegate.backgroundColor != backgroundColor;
  }
}

// ==========================================
// ВСТРОЕННЫЙ ПЛЕЕР ДЛЯ ГОЛОСОВЫХ СООБЩЕНИЙ
// ==========================================
class InlineVoiceNotePlayer extends StatefulWidget {
  final String audioPath;
  const InlineVoiceNotePlayer({super.key, required this.audioPath});

  @override
  State<InlineVoiceNotePlayer> createState() => _InlineVoiceNotePlayerState();
}

class _InlineVoiceNotePlayerState extends State<InlineVoiceNotePlayer> {
  final AudioPlayer _audioPlayer = AudioPlayer();
  bool _isPlaying = false;
  bool _isInitializing = false;
  Duration _duration = Duration.zero;
  Duration _position = Duration.zero;
  bool _sourceInitialized = false;
  Timer? _fileWatchTimer;

  bool get _hasValidDuration => _duration.inMilliseconds > 0;

  @override
  void initState() {
    super.initState();
    _initAudioListeners();
    _checkAndSetupSource();
    _startFileAvailabilityPolling();
  }

  // Вынесем проверку в отдельный метод
  void _checkAndSetupSource() {
    if (widget.audioPath.isEmpty || _sourceInitialized || _isInitializing)
      return;

    final file = File(widget.audioPath);
    if (!file.existsSync()) {
      debugPrint('[AUDIO] Файл пока отсутствует на диске, ждем обновления...');
      return;
    }

    _isInitializing = true;
    if (mounted) {
      setState(() {});
    }

    _setupSource(widget.audioPath).whenComplete(() {
      if (!mounted) {
        _isInitializing = false;
        return;
      }
      setState(() {
        _isInitializing = false;
      });
    });
  }

  void _startFileAvailabilityPolling() {
    _fileWatchTimer?.cancel();
    if (widget.audioPath.isEmpty) return;

    final file = File(widget.audioPath);
    if (file.existsSync()) {
      if (!_sourceInitialized && !_isInitializing) {
        _checkAndSetupSource();
      }
      return;
    }

    _fileWatchTimer = Timer.periodic(const Duration(milliseconds: 250), (
      timer,
    ) {
      if (!mounted) {
        timer.cancel();
        return;
      }
      if (widget.audioPath.isEmpty) {
        timer.cancel();
        return;
      }
      final file = File(widget.audioPath);
      if (file.existsSync()) {
        timer.cancel();
        if (mounted) {
          setState(() {});
          _checkAndSetupSource();
        }
      }
    });
  }

  Future<void> _setupSource(String path) async {
    try {
      await _audioPlayer.stop();
      await _audioPlayer.setSource(DeviceFileSource(path));

      if (!mounted) return;
      setState(() {
        _sourceInitialized = true;
      });

      final d = await _audioPlayer.getDuration();
      if (!mounted) return;
      if (d != null && d.inMilliseconds > 0) {
        setState(() {
          _duration = d;
        });
      }

      debugPrint('[AUDIO] Источник успешно установлен для: $path');
    } catch (e) {
      debugPrint('[AUDIO ERROR] Ошибка установки источника: $e');
    }
  }

  @override
  void didUpdateWidget(covariant InlineVoiceNotePlayer oldWidget) {
    super.didUpdateWidget(oldWidget);

    final bool pathChanged = oldWidget.audioPath != widget.audioPath;
    final bool fileJustAppeared =
        widget.audioPath.isNotEmpty &&
        !_sourceInitialized &&
        File(widget.audioPath).existsSync();

    if (pathChanged) {
      _sourceInitialized = false;
      _position = Duration.zero;
      _duration = Duration.zero;
      _fileWatchTimer?.cancel();
    }

    if (pathChanged || fileJustAppeared) {
      debugPrint('[AUDIO_UPDATE] Реактивное обновление источника звука.');
      _checkAndSetupSource();
      _startFileAvailabilityPolling();
      if (_isFileAvailable && !_sourceInitialized && mounted) {
        setState(() {});
      }
    }
  }

  void _initAudioListeners() {
    _audioPlayer.onPlayerStateChanged.listen((state) {
      if (mounted) {
        setState(() {
          _isPlaying = state == PlayerState.playing;
          if (state == PlayerState.stopped || state == PlayerState.completed) {
            _position = Duration.zero;
          }
        });
      }
    });

    _audioPlayer.onDurationChanged.listen((newDuration) {
      // ЗАЩИТА ОТ ДЕРГАНИЯ: игнорируем пустые или некорректные ивенты от движка
      if (mounted && newDuration.inMilliseconds > 0) {
        setState(() {
          _duration = newDuration;
        });
      }
    });

    _audioPlayer.onPositionChanged.listen((newPosition) {
      if (!mounted) return;

      // СЛЕПАЯ ЗОНА (150мс): сглаживаем рывок ползунка в самом начале воспроизведения
      if (_isPlaying && newPosition.inMilliseconds < 150) return;

      setState(() {
        if (_duration.inMilliseconds > 0 && newPosition > _duration) {
          _position = _duration;
        } else {
          _position = newPosition;
        }
      });
    });
  }

  void _togglePlay() async {
    // Блокируем клик, если файл физически еще не скачан
    if (widget.audioPath.isEmpty || !File(widget.audioPath).existsSync())
      return;

    if (!_sourceInitialized) {
      _checkAndSetupSource();
    }

    if (_isPlaying) {
      await _audioPlayer.pause();
    } else {
      await _audioPlayer.play(DeviceFileSource(widget.audioPath));
    }
  }

  bool get _isFileAvailable =>
      widget.audioPath.isNotEmpty && File(widget.audioPath).existsSync();

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

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

  @override
  Widget build(BuildContext context) {
    final bool fileAvailable = _isFileAvailable;
    final bool hasDuration = _hasValidDuration;

    final bool isReady = fileAvailable && _sourceInitialized && hasDuration;
    final String statusText;
    if (!fileAvailable) {
      statusText = 'Загрузка...';
    } else if (!_sourceInitialized || !hasDuration) {
      statusText = 'Подготовка...';
    } else {
      statusText =
          "${_formatDuration(_position)} / ${_formatDuration(_duration)}";
    }

    final double durationMs = _duration.inMilliseconds.toDouble();
    final double positionMs = _position.inMilliseconds.toDouble();
    final bool canSeek = hasDuration;
    final double safeMax = durationMs > 0 ? durationMs : 1.0;
    final double safeValue = durationMs > 0
        ? positionMs.clamp(0.0, safeMax)
        : 0.0;

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
      width: 240,
      decoration: BoxDecoration(
        color: Colors.black12,
        borderRadius: BorderRadius.circular(10),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          IconButton(
            icon: Icon(
              _isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled,
            ),
            iconSize: 36,
            color: fileAvailable ? Colors.white : Colors.white38,
            onPressed: fileAvailable ? _togglePlay : null,
          ),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                SliderTheme(
                  data: SliderTheme.of(context).copyWith(
                    trackHeight: 3,
                    padding: EdgeInsets.zero,
                    thumbShape: const RoundSliderThumbShape(
                      enabledThumbRadius: 5,
                      elevation: 0,
                    ),
                    overlayShape: const RoundSliderOverlayShape(
                      overlayRadius: 8,
                    ),
                  ),
                  child: Container(
                    height: 20,
                    alignment: Alignment.center,
                    child: Slider(
                      activeColor: isReady ? Colors.white : Colors.white38,
                      inactiveColor: Colors.white60,
                      thumbColor: isReady ? Colors.white : Colors.white24,
                      min: 0.0,
                      max: safeMax,
                      value: safeValue,
                      onChanged: canSeek
                          ? (value) async {
                              await _audioPlayer.seek(
                                Duration(milliseconds: value.toInt()),
                              );
                            }
                          : null,
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 4,
                    vertical: 2,
                  ),
                  child: Text(
                    statusText,
                    style: const TextStyle(
                      fontSize: 11,
                      color: Colors.white70,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
