import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:material_symbols_icons/symbols.dart';

import 'package:komet/backend/modules/messages.dart';
import 'package:komet/core/config/app_chat_chrome.dart';
import 'package:komet/core/config/app_colors.dart';
import 'package:komet/core/config/app_composer_background.dart';
import 'package:komet/core/config/app_composer_style.dart';
import 'package:komet/core/config/app_frost.dart';
import 'package:komet/frontend/screens/chats/chat/upload_status.dart';
import 'package:komet/frontend/screens/chats/chat/video_note_controller.dart';
import 'package:komet/frontend/screens/chats/chat/voice_record_controller.dart';
import 'package:komet/frontend/widgets/glossy_pill.dart';
import 'package:komet/frontend/widgets/liquid_glass.dart';
import 'package:komet/frontend/widgets/rich_message_controller.dart';

class ComposerInputBar extends StatelessWidget {
  const ComposerInputBar({
    super.key,
    required this.chatType,
    required this.chrome,
    required this.style,
    required this.background,
    this.backdropKey,
    required this.attachAnim,
    required this.replyTo,
    required this.forwardMessages,
    required this.myId,
    required this.hasText,
    required this.uploadStatus,
    required this.messageController,
    required this.messageFocusNode,
    required this.voiceRec,
    required this.note,
    required this.onToggleStickerPanel,
    required this.onSendText,
    required this.onScheduleMessage,
    required this.onOpenAttach,
    required this.onOpenAttachScheduled,
    required this.onSendHistory,
    required this.onCancelReply,
    required this.onCancelForward,
    this.onPickReplyChat,
    required this.formatElapsed,
    required this.contextMenuBuilder,
    required this.isMuted,
    required this.onToggleMute,
    this.channelSubscribed = true,
    this.channelSubscribing = false,
    this.onSubscribe,
    this.showStickerButton = true,
    this.showAttachButton = true,
    this.forceSend = false,
    this.hintText = 'Message',
    this.bottomSafe = true,
  });

  final String chatType;
  final ChatChromeStyle chrome;
  final ComposerStyle style;
  final ComposerBackground background;
  final BackdropKey? backdropKey;
  final Animation<double> attachAnim;
  final ValueListenable<CachedMessage?> replyTo;
  final ValueListenable<List<CachedMessage>> forwardMessages;
  final int myId;
  final ValueListenable<bool> hasText;
  final ValueListenable<UploadStatus> uploadStatus;
  final RichMessageController messageController;
  final FocusNode messageFocusNode;
  final VoiceRecordController voiceRec;
  final VideoNoteController note;
  final VoidCallback onToggleStickerPanel;
  final VoidCallback onSendText;
  final VoidCallback onScheduleMessage;
  final VoidCallback onOpenAttach;
  final VoidCallback onOpenAttachScheduled;
  final Future<void> Function(FileHistoryEntry entry) onSendHistory;
  final VoidCallback onCancelReply;
  final VoidCallback onCancelForward;
  final VoidCallback? onPickReplyChat;
  final String Function(int ms) formatElapsed;
  final Widget Function(BuildContext, EditableTextState) contextMenuBuilder;
  final bool isMuted;
  final VoidCallback onToggleMute;
  final bool channelSubscribed;
  final bool channelSubscribing;
  final VoidCallback? onSubscribe;
  final bool showStickerButton;
  final bool showAttachButton;
  final bool forceSend;
  final String hintText;
  final bool bottomSafe;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<List<CachedMessage>>(
      valueListenable: forwardMessages,
      builder: (context, forwards, _) => _build(context, forwards),
    );
  }

  Widget _build(BuildContext context, List<CachedMessage> forwards) {
    final cs = Theme.of(context).colorScheme;
    final mutedIcon = cs.onSurfaceVariant.withValues(alpha: 0.85);
    final hasForward = forwards.isNotEmpty;

    if (chatType == "CHANNEL" && !hasForward) {
      if (!channelSubscribed) {
        return SafeArea(
          child: Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: 12.0,
              vertical: 8.0,
            ),
            child: GlossyPill(
              onTap: channelSubscribing ? null : onSubscribe,
              color: cs.primary,
              borderRadius: BorderRadius.circular(28),
              padding: const EdgeInsets.symmetric(vertical: 16),
              depth: 8,
              borderSide: BorderSide(
                color: cs.outlineVariant.withValues(alpha: 0.5),
                width: 0.5,
              ),
              child: SizedBox(
                width: double.infinity,
                child: Center(
                  child: channelSubscribing
                      ? SizedBox(
                          width: 22,
                          height: 22,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            color: cs.onPrimary,
                          ),
                        )
                      : Text(
                          'Подписаться',
                          style: TextStyle(
                            color: cs.onPrimary,
                            fontSize: 16,
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                ),
              ),
            ),
          ),
        );
      }
      return SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
          child: GlossyPill(
            onTap: onToggleMute,
            color: Color.alphaBlend(
              cs.surfaceContainerHighest.withValues(alpha: 0.92),
              cs.surface,
            ),
            borderRadius: BorderRadius.circular(28),
            padding: const EdgeInsets.symmetric(vertical: 16),
            depth: 8,
            borderSide: BorderSide(
              color: cs.outlineVariant.withValues(alpha: 0.5),
              width: 0.5,
            ),
            child: SizedBox(
              width: double.infinity,
              child: Center(
                child: Text(
                  isMuted ? 'Включить уведомления' : 'Отключить уведомления',
                  style: TextStyle(
                    color: cs.onSurface,
                    fontSize: 16,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ),
          ),
        ),
      );
    }

    final bar = SafeArea(
      bottom: bottomSafe,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          _messagePreview(cs, forwards),
          Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: 12.0,
              vertical: 8.0,
            ),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                Expanded(
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 200),
                    constraints: const BoxConstraints(
                      minHeight: 54,
                      maxHeight: 180,
                    ),
                    child: _fieldSurface(
                      cs,
                      Stack(
                        alignment: Alignment.center,
                        children: [
                          AnimatedBuilder(
                            animation: attachAnim,
                            builder: (context, child) {
                              final t = attachAnim.value;
                              return IgnorePointer(
                                ignoring: t > 0.5,
                                child: Opacity(
                                  opacity: (1 - t).clamp(0.0, 1.0),
                                  child: child,
                                ),
                              );
                            },
                            child: Padding(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 14,
                              ),
                              child: Row(
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  if (showStickerButton) ...[
                                    GestureDetector(
                                      behavior: HitTestBehavior.opaque,
                                      onTap: onToggleStickerPanel,
                                      child: Icon(
                                        Symbols.face,
                                        color: mutedIcon,
                                        size: 24,
                                        weight: 400,
                                      ),
                                    ),
                                    const SizedBox(width: 12),
                                  ],
                                  Expanded(
                                    child: Focus(
                                      onKeyEvent: (node, event) {
                                        if (event is KeyDownEvent &&
                                            event.logicalKey ==
                                                LogicalKeyboardKey.enter &&
                                            !HardwareKeyboard
                                                .instance
                                                .isShiftPressed) {
                                          if (hasText.value ||
                                              hasForward ||
                                              forceSend) {
                                            onSendText();
                                          }
                                          return KeyEventResult.handled;
                                        }
                                        return KeyEventResult.ignored;
                                      },
                                      child: TextField(
                                        controller: messageController,
                                        focusNode: messageFocusNode,
                                        style: TextStyle(
                                          color: cs.onSurface,
                                          fontSize: 16,
                                        ),
                                        maxLines: null,
                                        keyboardType: TextInputType.multiline,
                                        textAlignVertical:
                                            TextAlignVertical.center,
                                        contextMenuBuilder: contextMenuBuilder,
                                        decoration: InputDecoration(
                                          hintText: hintText,
                                          hintStyle: TextStyle(
                                            color: cs.onSurfaceVariant,
                                            fontSize: 16,
                                          ),
                                          border: InputBorder.none,
                                          isDense: true,
                                          contentPadding:
                                              const EdgeInsets.symmetric(
                                                vertical: 14,
                                              ),
                                        ),
                                      ),
                                    ),
                                  ),
                                  if (showAttachButton)
                                    _AttachButton(
                                      hasText: hasText,
                                      onOpen: onOpenAttach,
                                      onLongOpen: onOpenAttachScheduled,
                                      uploadStatus: uploadStatus,
                                      mutedIcon: mutedIcon,
                                      cs: cs,
                                    ),
                                ],
                              ),
                            ),
                          ),
                          Positioned(
                            left: 0,
                            right: 0,
                            bottom: 0,
                            child: SizedBox(
                              height: 54,
                              child: AnimatedBuilder(
                                animation: attachAnim,
                                builder: (context, child) {
                                  final t = attachAnim.value;
                                  return IgnorePointer(
                                    ignoring: t < 0.5,
                                    child: Opacity(
                                      opacity: t.clamp(0.0, 1.0),
                                      child: child,
                                    ),
                                  );
                                },
                                child: _HistoryStrip(
                                  anim: attachAnim,
                                  cs: cs,
                                  onTapEntry: onSendHistory,
                                ),
                              ),
                            ),
                          ),
                          Positioned.fill(
                            child: ValueListenableBuilder<bool>(
                              valueListenable: voiceRec.isRecording,
                              builder: (context, recording, _) => IgnorePointer(
                                ignoring: !recording,
                                child: AnimatedSlide(
                                  offset: recording
                                      ? Offset.zero
                                      : const Offset(0.06, 0),
                                  duration: const Duration(milliseconds: 200),
                                  curve: Curves.easeOutCubic,
                                  child: AnimatedOpacity(
                                    opacity: recording ? 1 : 0,
                                    duration: const Duration(milliseconds: 180),
                                    curve: Curves.easeOut,
                                    child: _voiceRecordingIndicator(cs),
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
                AnimatedBuilder(
                  animation: attachAnim,
                  builder: (context, child) {
                    final t = attachAnim.value;
                    return ClipRect(
                      clipper: _ButtonClipper(t),
                      child: Align(
                        alignment: Alignment.centerLeft,
                        widthFactor: (1 - t).clamp(0.0, 1.0),
                        child: child,
                      ),
                    );
                  },
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const SizedBox(width: 8),
                      AnimatedBuilder(
                        animation: attachAnim,
                        builder: (context, child) {
                          final t = attachAnim.value;
                          return Transform.translate(
                            offset: Offset(t * 80, 0),
                            child: Opacity(
                              opacity: (1 - t * 1.5).clamp(0.0, 1.0),
                              child: child,
                            ),
                          );
                        },
                        child: ValueListenableBuilder<bool>(
                          valueListenable: hasText,
                          builder: (context, hasText, _) =>
                              ValueListenableBuilder<bool>(
                                valueListenable: voiceRec.locked,
                                builder: (context, locked, _) =>
                                    ValueListenableBuilder<bool>(
                                      valueListenable: voiceRec.isRecording,
                                      builder: (context, recording, _) =>
                                          ValueListenableBuilder<bool>(
                                            valueListenable: note.videoNoteMode,
                                            builder: (context, videoMode, _) {
                                              final sendMode =
                                                  hasText ||
                                                  hasForward ||
                                                  locked ||
                                                  forceSend;
                                              final pill = _actionSurface(
                                                color: _flat
                                                    ? Colors.transparent
                                                    : sendMode
                                                    ? cs.primary
                                                    : recording
                                                    ? cs.error
                                                    : _frost
                                                    ? AppFrost.inputTint(cs)
                                                    : cs.surfaceContainerHighest,
                                                onTap:
                                                    (hasText ||
                                                        hasForward ||
                                                        forceSend)
                                                    ? onSendText
                                                    : locked
                                                    ? () => voiceRec.stop(
                                                        cancel: false,
                                                      )
                                                    : null,
                                                onLongPress:
                                                    (hasText &&
                                                        !forceSend &&
                                                        !hasForward)
                                                    ? onScheduleMessage
                                                    : null,
                                                child: SizedBox(
                                                  width: 54,
                                                  height: 54,
                                                  child: Center(
                                                    child: Icon(
                                                      sendMode
                                                          ? Symbols.send
                                                          : videoMode
                                                          ? Symbols.videocam
                                                          : Symbols.mic,
                                                      color: _flat
                                                          ? (sendMode
                                                                ? cs.primary
                                                                : recording
                                                                ? cs.error
                                                                : cs.onSurfaceVariant)
                                                          : sendMode
                                                          ? cs.onPrimary
                                                          : recording
                                                          ? cs.onError
                                                          : cs.onSurface,
                                                      size: 24,
                                                      weight: 400,
                                                    ),
                                                  ),
                                                ),
                                              );
                                              final visual =
                                                  _recordingButtonVisual(
                                                    pill: pill,
                                                    cs: cs,
                                                    active:
                                                        recording && !locked,
                                                  );
                                              final voiceEnabled =
                                                  !sendMode && !forceSend;
                                              return GestureDetector(
                                                onTap: voiceEnabled
                                                    ? note.toggleMode
                                                    : null,
                                                onLongPressStart: voiceEnabled
                                                    ? (_) => videoMode
                                                          ? note.start()
                                                          : voiceRec.start()
                                                    : null,
                                                onLongPressMoveUpdate:
                                                    voiceEnabled
                                                    ? (d) => videoMode
                                                          ? note.handleDrag(
                                                              d.offsetFromOrigin,
                                                            )
                                                          : voiceRec.handleDrag(
                                                              d.offsetFromOrigin,
                                                            )
                                                    : null,
                                                onLongPressEnd: voiceEnabled
                                                    ? (_) => videoMode
                                                          ? note.handleEnd()
                                                          : voiceRec.handleEnd()
                                                    : null,
                                                child: visual,
                                              );
                                            },
                                          ),
                                    ),
                              ),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );

    return _barSurface(cs, bar);
  }

  bool get _flat => style == ComposerStyle.materialYou;

  bool get _frost => ComposerMaterial.isFrost(background);

  bool get _liquid => ComposerMaterial.isLiquid(background);

  bool get _translucent => _frost || _liquid;

  Widget _barSurface(ColorScheme cs, Widget child) {
    if (!_flat || _translucent) return child;
    return DecoratedBox(
      decoration: BoxDecoration(
        color: cs.surface,
        border: Border(top: AppFrost.hairline(cs)),
      ),
      child: child,
    );
  }

  Widget _fieldSurface(ColorScheme cs, Widget child) {
    if (_flat) return child;
    return GlossyPill(
      color: _translucent
          ? AppFrost.inputTint(cs)
          : Color.alphaBlend(
              cs.surfaceContainerHighest.withValues(alpha: 0.92),
              cs.surface,
            ),
      blurSigma: _frost ? AppFrost.sigma : null,
      liquid: _liquid,
      backdropKey: backdropKey,
      borderRadius: BorderRadius.circular(28),
      depth: 8,
      borderSide: BorderSide(
        color: cs.outlineVariant.withValues(alpha: 0.5),
        width: 0.5,
      ),
      child: child,
    );
  }

  Widget _actionSurface({
    required Color color,
    required Widget child,
    VoidCallback? onTap,
    VoidCallback? onLongPress,
  }) {
    if (_flat) {
      return Material(
        color: color,
        shape: const CircleBorder(),
        clipBehavior: Clip.antiAlias,
        child: onTap == null && onLongPress == null
            ? child
            : InkWell(onTap: onTap, onLongPress: onLongPress, child: child),
      );
    }
    return GlossyPill(
      color: color,
      blurSigma: _frost ? AppFrost.sigma : null,
      liquid: _liquid,
      backdropKey: backdropKey,
      borderRadius: BorderRadius.circular(27),
      onTap: onTap,
      onLongPress: onLongPress,
      depth: 8,
      child: child,
    );
  }

  Widget _replyIconButton(ColorScheme cs) {
    final icon = Icon(Symbols.reply, size: 20, color: cs.primary);
    if (onPickReplyChat == null) return icon;
    return InkWell(
      onTap: onPickReplyChat,
      customBorder: const CircleBorder(),
      child: Padding(padding: const EdgeInsets.all(4), child: icon),
    );
  }

  Widget _messagePreview(ColorScheme cs, List<CachedMessage> forwards) {
    if (forwards.isNotEmpty) return _forwardPreview(cs, forwards);
    return _replyPreview(cs);
  }

  Widget _forwardPreview(ColorScheme cs, List<CachedMessage> messages) {
    final first = messages.first;
    final senderName = ContactCache.get(first.senderId);
    final info = ReplyInfo(
      senderId: first.senderId,
      text: first.text,
      attachments: first.attachments,
    );
    final preview = info.previewText();
    final title = messages.length == 1
        ? first.senderId == myId
              ? 'Пересылка от вас'
              : senderName == null
              ? 'Пересылка сообщения'
              : 'Пересылка от $senderName'
        : 'Пересылка: ${_forwardCount(messages.length)}';
    final row = Padding(
      padding: const EdgeInsets.fromLTRB(16, 6, 8, 2),
      child: Row(
        children: [
          Icon(Symbols.forward, size: 20, color: cs.primary),
          const SizedBox(width: 10),
          Container(width: 2, height: 34, color: cs.primary),
          const SizedBox(width: 10),
          Expanded(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(
                    color: cs.primary,
                    fontSize: 13,
                    fontWeight: FontWeight.w600,
                  ),
                ),
                if (preview.isNotEmpty)
                  Text(
                    preview,
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
                  ),
              ],
            ),
          ),
          IconButton(
            icon: const Icon(Symbols.close, size: 20),
            color: cs.onSurfaceVariant,
            onPressed: onCancelForward,
          ),
        ],
      ),
    );
    return _previewSurface(cs, row);
  }

  String _forwardCount(int count) {
    final last = count % 10;
    final lastTwo = count % 100;
    if (last == 1 && lastTwo != 11) return '$count сообщение';
    if (last >= 2 && last <= 4 && (lastTwo < 12 || lastTwo > 14)) {
      return '$count сообщения';
    }
    return '$count сообщений';
  }

  Widget _replyPreview(ColorScheme cs) {
    return ValueListenableBuilder<CachedMessage?>(
      valueListenable: replyTo,
      builder: (context, reply, _) {
        if (reply == null) return const SizedBox.shrink();
        final name = reply.senderId == myId
            ? 'Вы'
            : (ContactCache.get(reply.senderId) ?? 'Сообщение');
        final info = ReplyInfo(
          senderId: reply.senderId,
          text: reply.text,
          attachments: reply.attachments,
        );
        final preview = info.previewText();
        final row = Padding(
          padding: const EdgeInsets.fromLTRB(16, 6, 8, 2),
          child: Row(
            children: [
              _replyIconButton(cs),
              const SizedBox(width: 10),
              Container(width: 2, height: 34, color: cs.primary),
              const SizedBox(width: 10),
              Expanded(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Ответ $name',
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                        color: cs.primary,
                        fontSize: 13,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    if (preview.isNotEmpty)
                      Text(
                        preview,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: TextStyle(
                          color: cs.onSurfaceVariant,
                          fontSize: 13,
                        ),
                      ),
                  ],
                ),
              ),
              IconButton(
                icon: const Icon(Symbols.close, size: 20),
                color: cs.onSurfaceVariant,
                onPressed: onCancelReply,
              ),
            ],
          ),
        );
        return _previewSurface(cs, row);
      },
    );
  }

  Widget _previewSurface(ColorScheme cs, Widget child) {
    if (_flat && _translucent) return child;
    if (!_translucent && chrome != ChatChromeStyle.transparent) return child;
    return GlassSurface(
      liquid: _liquid,
      frostTint: AppFrost.panelTint(cs),
      border: Border(top: AppFrost.hairline(cs)),
      backdropKey: backdropKey,
      child: child,
    );
  }

  Widget _recordingButtonVisual({
    required Widget pill,
    required ColorScheme cs,
    required bool active,
  }) {
    return TweenAnimationBuilder<double>(
      tween: Tween(begin: 0.0, end: active ? 1.0 : 0.0),
      duration: const Duration(milliseconds: 220),
      curve: Curves.easeOut,
      builder: (context, a, _) {
        if (a <= 0.001) return pill;
        return ValueListenableBuilder<double>(
          valueListenable: voiceRec.amplitude,
          builder: (context, amp, _) => TweenAnimationBuilder<double>(
            tween: Tween(begin: 0.0, end: amp),
            duration: const Duration(milliseconds: 110),
            builder: (context, v, _) {
              final glow = a * (88.0 + v * 76.0);
              return Stack(
                clipBehavior: Clip.none,
                alignment: Alignment.center,
                children: [
                  Positioned(
                    left: 27 - glow / 2,
                    top: 27 - glow / 2,
                    child: Container(
                      width: glow,
                      height: glow,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: cs.error.withValues(
                          alpha: a * (0.16 + v * 0.12),
                        ),
                      ),
                    ),
                  ),
                  _voiceLockChip(cs),
                  Transform.scale(
                    scale: 1.0 + a * 0.14 + a * v * 0.24,
                    child: pill,
                  ),
                ],
              );
            },
          ),
        );
      },
    );
  }

  Widget _voiceLockChip(ColorScheme cs) {
    return Positioned(
      bottom: 62,
      child: ValueListenableBuilder<double>(
        valueListenable: voiceRec.lockDrag,
        builder: (context, lock, _) => Opacity(
          opacity: (0.5 + lock * 0.5).clamp(0.0, 1.0),
          child: Transform.translate(
            offset: Offset(0, lock * 12),
            child: Container(
              padding: const EdgeInsets.symmetric(vertical: 7, horizontal: 6),
              decoration: BoxDecoration(
                color: Color.alphaBlend(
                  cs.surfaceContainerHighest.withValues(alpha: 0.96),
                  cs.surface,
                ),
                borderRadius: BorderRadius.circular(16),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withValues(alpha: 0.15),
                    blurRadius: 6,
                  ),
                ],
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Icon(
                    Symbols.lock,
                    size: 16,
                    color: lock > 0.6 ? cs.primary : cs.onSurfaceVariant,
                  ),
                  Icon(
                    Symbols.keyboard_arrow_up,
                    size: 14,
                    color: cs.onSurfaceVariant,
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _voiceRecordingIndicator(ColorScheme cs) {
    return Container(
      color: Color.alphaBlend(
        cs.surfaceContainerHighest.withValues(alpha: 0.92),
        cs.surface,
      ),
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Row(
        children: [
          ValueListenableBuilder<double>(
            valueListenable: voiceRec.amplitude,
            builder: (context, amp, child) => TweenAnimationBuilder<double>(
              tween: Tween(begin: 0, end: amp),
              duration: const Duration(milliseconds: 120),
              builder: (context, v, child) =>
                  Transform.scale(scale: 1.0 + v * 0.7, child: child),
              child: child,
            ),
            child: _RecordingDot(color: cs.error),
          ),
          const SizedBox(width: 12),
          ValueListenableBuilder<int>(
            valueListenable: voiceRec.elapsedMs,
            builder: (context, ms, _) => Text(
              formatElapsed(ms),
              style: TextStyle(
                color: cs.onSurface,
                fontSize: 16,
                fontFeatures: const [ui.FontFeature.tabularFigures()],
              ),
            ),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: ValueListenableBuilder<double>(
              valueListenable: voiceRec.cancelDrag,
              builder: (context, drag, _) {
                if (drag > 0.01) {
                  return Opacity(
                    opacity: (0.45 + drag * 0.55).clamp(0.0, 1.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        Icon(
                          Symbols.arrow_back,
                          size: 16,
                          color: cs.onSurfaceVariant,
                        ),
                        const SizedBox(width: 6),
                        Text(
                          'Отмена',
                          style: TextStyle(
                            color: cs.onSurfaceVariant,
                            fontSize: 14,
                          ),
                        ),
                      ],
                    ),
                  );
                }
                return SizedBox(
                  height: 26,
                  child: ValueListenableBuilder<int>(
                    valueListenable: voiceRec.waveRev,
                    builder: (context, _, _) => CustomPaint(
                      size: Size.infinite,
                      painter: _LiveWavePainter(
                        amps: voiceRec.amps,
                        color: cs.primary.withValues(alpha: 0.85),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
          const SizedBox(width: 8),
          ValueListenableBuilder<bool>(
            valueListenable: voiceRec.locked,
            builder: (context, locked, _) => locked
                ? GestureDetector(
                    onTap: () => voiceRec.stop(cancel: true),
                    behavior: HitTestBehavior.opaque,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 4),
                      child: Icon(Symbols.delete, size: 22, color: cs.error),
                    ),
                  )
                : Text(
                    '‹ влево — отмена',
                    style: TextStyle(color: cs.mutedText, fontSize: 11),
                  ),
          ),
        ],
      ),
    );
  }
}

class _AttachButton extends StatelessWidget {
  final ValueListenable<bool> hasText;
  final VoidCallback onOpen;
  final VoidCallback onLongOpen;
  final ValueListenable<UploadStatus> uploadStatus;
  final Color mutedIcon;
  final ColorScheme cs;

  const _AttachButton({
    required this.hasText,
    required this.onOpen,
    required this.onLongOpen,
    required this.uploadStatus,
    required this.mutedIcon,
    required this.cs,
  });

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: Listenable.merge([hasText, uploadStatus]),
      builder: (context, _) {
        final isText = hasText.value;
        final status = uploadStatus.value;
        final iconColor = status.awaitingResponse
            ? cs.primary
            : (status.active
                  ? cs.onSurfaceVariant.withValues(alpha: 0.5)
                  : mutedIcon);
        final disabled = isText || status.active;
        final onTap = disabled ? null : onOpen;
        final onLongPress = disabled ? null : onLongOpen;
        return AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          width: isText ? 0 : 36,
          child: AnimatedOpacity(
            duration: const Duration(milliseconds: 200),
            opacity: isText ? 0 : 1,
            child: isText
                ? const SizedBox.shrink()
                : GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    onTap: onTap,
                    onLongPress: onLongPress,
                    child: Padding(
                      padding: const EdgeInsets.only(left: 12),
                      child: Stack(
                        alignment: Alignment.center,
                        children: [
                          if (status.active)
                            SizedBox(
                              width: 30,
                              height: 30,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                value: status.progressValue,
                                color: cs.primary,
                              ),
                            ),
                          Icon(
                            Symbols.attachment,
                            color: iconColor,
                            size: 22,
                            weight: 400,
                          ),
                        ],
                      ),
                    ),
                  ),
          ),
        );
      },
    );
  }
}

class _HistoryStrip extends StatelessWidget {
  final Animation<double> anim;
  final ColorScheme cs;
  final Future<void> Function(FileHistoryEntry entry) onTapEntry;

  const _HistoryStrip({
    required this.anim,
    required this.cs,
    required this.onTapEntry,
  });

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<List<FileHistoryEntry>>(
      valueListenable: FileHistoryCache.notifier,
      builder: (context, history, _) {
        if (history.isEmpty) {
          return Center(
            child: AnimatedBuilder(
              animation: anim,
              builder: (context, _) {
                final v = anim.value.clamp(0.0, 1.0);
                return Opacity(
                  opacity: v,
                  child: Text(
                    'история пуста...',
                    style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
                  ),
                );
              },
            ),
          );
        }
        return ListView.builder(
          scrollDirection: Axis.horizontal,
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
          itemCount: history.length,
          itemBuilder: (ctx, idx) {
            final e = history[idx];
            final startInterval = (idx * 0.05).clamp(0.0, 0.45);
            return AnimatedBuilder(
              animation: anim,
              builder: (context, child) {
                final raw = ((anim.value - startInterval) / 0.45).clamp(
                  0.0,
                  1.0,
                );
                final v = Curves.easeOutCubic.transform(raw);
                return Opacity(
                  opacity: v,
                  child: Transform.translate(
                    offset: Offset(-14 * (1 - v), 0),
                    child: child,
                  ),
                );
              },
              child: Container(
                width: 54,
                margin: const EdgeInsets.symmetric(horizontal: 3),
                decoration: BoxDecoration(
                  color: cs.surfaceContainerLow,
                  borderRadius: BorderRadius.circular(10),
                  border: Border.all(
                    color: cs.outlineVariant.withValues(alpha: 0.3),
                  ),
                ),
                child: Stack(
                  children: [
                    Positioned.fill(
                      child: GestureDetector(
                        behavior: HitTestBehavior.opaque,
                        onTap: () => onTapEntry(e),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Icon(
                              _iconForFilename(e.filename),
                              color: cs.onSurfaceVariant,
                              size: 22,
                            ),
                            const SizedBox(height: 2),
                            Padding(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 3,
                              ),
                              child: Text(
                                _labelForEntry(e),
                                style: TextStyle(
                                  color: cs.onSurfaceVariant,
                                  fontSize: 9,
                                ),
                                overflow: TextOverflow.ellipsis,
                                maxLines: 1,
                                textAlign: TextAlign.center,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    Positioned(
                      top: -2,
                      right: -2,
                      child: GestureDetector(
                        behavior: HitTestBehavior.opaque,
                        onTap: () => FileHistoryCache.remove(e.fileId),
                        child: Container(
                          width: 18,
                          height: 18,
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: cs.surfaceContainerHighest,
                            shape: BoxShape.circle,
                            border: Border.all(
                              color: cs.outlineVariant.withValues(alpha: 0.5),
                              width: 0.5,
                            ),
                          ),
                          child: Icon(
                            Symbols.close,
                            size: 12,
                            color: cs.onSurfaceVariant,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          },
        );
      },
    );
  }
}

class _ButtonClipper extends CustomClipper<Rect> {
  final double t;
  const _ButtonClipper(this.t);

  @override
  Rect getClip(Size size) {
    if (t <= 0.001) {
      return Rect.fromLTRB(-120, -260, size.width + 120, size.height + 40);
    }
    return Rect.fromLTRB(0, 0, size.width, size.height);
  }

  @override
  bool shouldReclip(_ButtonClipper old) => old.t != t;
}

class _RecordingDot extends StatefulWidget {
  final Color color;
  const _RecordingDot({required this.color});

  @override
  State<_RecordingDot> createState() => _RecordingDotState();
}

class _RecordingDotState extends State<_RecordingDot>
    with SingleTickerProviderStateMixin {
  late final AnimationController _c = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 900),
  )..repeat(reverse: true);

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

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: Tween<double>(begin: 1.0, end: 0.25).animate(_c),
      child: Container(
        width: 12,
        height: 12,
        decoration: BoxDecoration(color: widget.color, shape: BoxShape.circle),
      ),
    );
  }
}

class _LiveWavePainter extends CustomPainter {
  final List<double> amps;
  final Color color;

  const _LiveWavePainter({required this.amps, required this.color});

  @override
  void paint(Canvas canvas, Size size) {
    const slot = 5.0;
    const barW = 3.0;
    final count = (size.width / slot).floor();
    if (count <= 0 || amps.isEmpty) return;

    final start = amps.length > count ? amps.length - count : 0;
    final visible = amps.sublist(start);
    final center = size.height / 2;
    final paint = Paint()..color = color;
    final offset = size.width - visible.length * slot;

    for (var i = 0; i < visible.length; i++) {
      final h = (visible[i] * size.height).clamp(2.0, size.height);
      final x = offset + i * slot + (slot - barW) / 2;
      canvas.drawRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTWH(x, center - h / 2, barW, h),
          const Radius.circular(barW / 2),
        ),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(_LiveWavePainter old) => true;
}

String _labelForEntry(FileHistoryEntry e) {
  final n = e.filename;
  if (n == null || n.isEmpty) return e.fileId.toString();
  final lastDot = n.lastIndexOf('.');
  return lastDot > 0 ? n.substring(0, lastDot) : n;
}

IconData _iconForFilename(String? name) {
  if (name == null || !name.contains('.')) return Symbols.description;
  final ext = name.split('.').last.toLowerCase();
  switch (ext) {
    case 'jpg':
    case 'jpeg':
    case 'png':
    case 'gif':
    case 'webp':
    case 'bmp':
    case 'heic':
    case 'heif':
      return Symbols.image;
    case 'mp4':
    case 'mov':
    case 'avi':
    case 'mkv':
    case 'webm':
    case '3gp':
      return Symbols.movie;
    case 'mp3':
    case 'wav':
    case 'ogg':
    case 'flac':
    case 'm4a':
    case 'aac':
      return Symbols.audio_file;
    case 'pdf':
      return Symbols.picture_as_pdf;
    case 'zip':
    case 'rar':
    case '7z':
    case 'tar':
    case 'gz':
      return Symbols.folder_zip;
    case 'doc':
    case 'docx':
    case 'txt':
    case 'rtf':
    case 'odt':
    case 'md':
      return Symbols.article;
    case 'xls':
    case 'xlsx':
    case 'csv':
      return Symbols.table_chart;
    case 'ppt':
    case 'pptx':
      return Symbols.slideshow;
    case 'dart':
    case 'js':
    case 'ts':
    case 'py':
    case 'java':
    case 'kt':
    case 'swift':
    case 'cpp':
    case 'c':
    case 'h':
    case 'rs':
    case 'go':
    case 'rb':
    case 'php':
    case 'html':
    case 'css':
    case 'json':
    case 'xml':
    case 'yaml':
    case 'yml':
      return Symbols.code;
    default:
      return Symbols.description;
  }
}
