import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/chat/mitra_chat_notifier.dart';
import '../../../core/chat/extension_notifier.dart';
import '../../../core/chat/sensitivity_config_provider.dart';
import '../../../core/chat/widgets/sensitivity_badge.dart';
import '../../../core/chat/widgets/sensitivity_theme.dart';
import '../../../core/constants.dart';
import '../../../core/theme/halo_tokens.dart';
import '../../../core/theme/widgets/halo_orb.dart';

// Mitra bubble gradient — matches `BestieChatV5` (figma-bestie/.../v5.jsx:282)
// pink → purple direction (135deg ≈ topLeft → bottomRight).
const _kMitraBubbleGradient = LinearGradient(
  begin: Alignment.topLeft,
  end: Alignment.bottomRight,
  colors: [Color(0xFFC44979), Color(0xFF8B5CF6)],
);

class MitraChatScreen extends ConsumerStatefulWidget {
  final String sessionId;
  final String customerName;

  const MitraChatScreen({super.key, required this.sessionId, required this.customerName});

  @override
  ConsumerState<MitraChatScreen> createState() => _MitraChatScreenState();
}

class _MitraChatScreenState extends ConsumerState<MitraChatScreen> {
  final _messageController = TextEditingController();
  final _goodbyeController = TextEditingController();
  final _scrollController = ScrollController();
  Timer? _typingThrottle;

  @override
  void initState() {
    super.initState();
    Future.microtask(() {
      ref.read(mitraChatProvider.notifier).connect(widget.sessionId);
    });
  }

  @override
  void deactivate() {
    // Disconnect runs here, NOT in dispose(): modern Riverpod invalidates
    // `ref` the instant dispose() starts, and the resulting silent error
    // corrupts the widget-tree finalize (next screen freezes). deactivate()
    // runs BEFORE dispose() while `ref` is still valid. Same fix pattern
    // applied in client_app/home_screen + payment_screen on 2026-05-14.
    // ignore: discarded_futures
    ref.read(mitraChatProvider.notifier).disconnect();
    super.deactivate();
  }

  @override
  void dispose() {
    _messageController.dispose();
    _goodbyeController.dispose();
    _scrollController.dispose();
    _typingThrottle?.cancel();
    super.dispose();
  }

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

  void _onTextChanged(String text) {
    if (_typingThrottle?.isActive ?? false) return;
    ref.read(mitraChatProvider.notifier).sendTyping();
    _typingThrottle = Timer(const Duration(seconds: 2), () {});
  }

  void _sendMessage() {
    final text = _messageController.text.trim();
    if (text.isEmpty) return;
    ref.read(mitraChatProvider.notifier).sendMessage(text);
    _messageController.clear();
    _scrollToBottom();
  }

  @override
  Widget build(BuildContext context) {
    // Parent build runs ONCE per lifecycle — there are no ref.watch calls here.
    // State changes (messages, typing, status updates, mode flip, sensitivity
    // flip) all rebuild only the leaf consumers that watch them:
    //   - _MitraChatSubtitle           → mode + sessionExpired (via .select)
    //   - _MitraChatTopicToggle        → topicSensitivity (via .select) + config
    //   - _MitraChatTimerAction        → mitraChatRemainingSecondsProvider
    //   - _MitraChatBodyContent        → full chatProvider + extensionProvider
    // Pattern mirrors client_app/chat_screen post-refactor (2026-05-14).
    ref.listen(mitraExtensionProvider, (prev, next) {
      if (next is ExtensionCompleteData) {
        context.go('/home');
      }
    });

    ref.listen(mitraChatProvider, (prev, next) {
      if (next is MitraChatConnectedData) {
        _scrollToBottom();
        final unread = next.messages
            .where((m) => m.senderType == UserType.customer && m.status != MessageStatus.read)
            .map((m) => m.id)
            .toList();
        if (unread.isNotEmpty) {
          ref.read(mitraChatProvider.notifier).markRead(unread);
        }
      }
    });

    // Orb seed derived from the sessionId — stable per session, identical
     // to the convention used by `undangan_screen.dart` for incoming requests.
     // We don't have a separate customerId in this screen scope (only name +
     // sessionId), and the spec explicitly allows hashing sessionId here.
    final orbSeed = widget.sessionId.hashCode;

    return Scaffold(
      backgroundColor: HaloTokens.bg,
      appBar: AppBar(
        backgroundColor: HaloTokens.surface,
        foregroundColor: HaloTokens.ink,
        elevation: 0.5,
        centerTitle: false,
        titleSpacing: 0,
        leading: IconButton(
          icon: const Icon(Icons.chevron_left, size: 28, color: HaloTokens.ink),
          onPressed: () => context.pop(),
        ),
        title: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            HaloOrb(size: 38, seed: orbSeed),
            const SizedBox(width: 10),
            Flexible(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _MitraChatTitleName(fallback: widget.customerName),
                  const _MitraChatSubtitle(),
                ],
              ),
            ),
          ],
        ),
        actions: [
          _MitraChatTopicToggle(sessionId: widget.sessionId),
          const _MitraChatTimerAction(),
        ],
      ),
      body: _MitraChatBodyContent(
        sessionId: widget.sessionId,
        customerName: widget.customerName,
        messageController: _messageController,
        goodbyeController: _goodbyeController,
        scrollController: _scrollController,
        onSend: _sendMessage,
        onTextChanged: _onTextChanged,
      ),
    );
  }
}

/// AppBar subtitle line. Watches only the `mode` flag and `sessionExpired`
/// flag (each via `.select`), so a WS message / typing / status update never
/// rebuilds this widget — only an actual mode flip or expiry transition does.
/// Mirrors `BestieChatV5` (figma-bestie/.../v5.jsx:239): "sesi aktif · Chat",
/// "sesi aktif · Voice", or "sesi berakhir" (color #A8410E when ended).
/// AppBar customer-name line. Reads customerDisplayName from the chat
/// connected-state via `.select` so the parent AppBar doesn't rebuild on
/// every chat-state change. Falls back to the route-arg `fallback` value
/// (which is "Customer" when reached via FCM tap — see notification_service
/// `_navigateFromMessage`).
class _MitraChatTitleName extends ConsumerWidget {
  final String fallback;
  const _MitraChatTitleName({required this.fallback});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final name = ref.watch(mitraChatProvider.select((s) {
      if (s is MitraChatConnectedData) return s.customerDisplayName;
      return null;
    }));
    final displayed = (name != null && name.isNotEmpty) ? name : fallback;
    return Text(
      displayed,
      overflow: TextOverflow.ellipsis,
      style: const TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.w700,
        color: HaloTokens.ink,
        height: 1.2,
      ),
    );
  }
}

class _MitraChatSubtitle extends ConsumerWidget {
  const _MitraChatSubtitle();

  static const _endedInk = Color(0xFFA8410E);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isCall = ref.watch(mitraChatProvider.select(
      (s) => s is MitraChatConnectedData && s.mode == SessionMode.call,
    ));
    final ended = ref.watch(mitraChatProvider.select(
      (s) => s is MitraChatConnectedData && s.sessionExpired,
    ));
    final text = ended
        ? 'sesi berakhir'
        : isCall
            ? 'sesi aktif · Voice'
            : 'sesi aktif · Chat';
    return Text(
      text,
      style: TextStyle(
        fontSize: 11,
        fontWeight: FontWeight.w400,
        color: ended ? _endedInk : HaloTokens.inkSoft,
        height: 1.2,
      ),
    );
  }
}

/// AppBar topic-sensitivity flag/lock action. Watches only `topicSensitivity`
/// (via `.select`) plus the `sensitivityConfigProvider`. Confirmation dialog
/// + snackbars + `flipTopic` call all live here so the parent screen doesn't
/// need to know about topic state.
class _MitraChatTopicToggle extends ConsumerStatefulWidget {
  final String sessionId;
  const _MitraChatTopicToggle({required this.sessionId});

  @override
  ConsumerState<_MitraChatTopicToggle> createState() => _MitraChatTopicToggleState();
}

class _MitraChatTopicToggleState extends ConsumerState<_MitraChatTopicToggle> {
  @override
  Widget build(BuildContext context) {
    final sensitivity = ref.watch(mitraChatProvider.select((s) {
      if (s is MitraChatConnectedData) return s.topicSensitivity;
      return null;
    }));
    if (sensitivity == null) return const SizedBox.shrink();
    final configAsync = ref.watch(sensitivityConfigProvider);
    final config = configAsync.value ?? SensitivityConfig.defaults;
    final isSensitive = sensitivity == TopicSensitivity.sensitive;
    final locked = config.oneWayLatch && isSensitive;

    return Tooltip(
      message: locked
          ? 'Sesi sudah terkunci sebagai topik sensitif'
          : isSensitive
              ? 'Tandai sebagai topik umum'
              : 'Tandai sebagai topik sensitif',
      child: IconButton(
        icon: Icon(
          isSensitive ? Icons.flag : Icons.outlined_flag,
          color: isSensitive ? SensitivityTheme.sensitive.badgeBg : Colors.grey.shade600,
        ),
        onPressed: locked ? null : () => _onTopicTogglePressed(sensitivity, config),
      ),
    );
  }

  Future<void> _onTopicTogglePressed(
    TopicSensitivity current,
    SensitivityConfig config,
  ) async {
    final toValue = current == TopicSensitivity.sensitive
        ? TopicSensitivity.regular
        : TopicSensitivity.sensitive;

    if (config.flipConfirmationEnabled) {
      final confirmed = await showDialog<bool>(
        context: context,
        builder: (ctx) => AlertDialog(
          title: Text(
            toValue == TopicSensitivity.sensitive
                ? 'Tandai sesi ini sebagai sensitif?'
                : 'Tandai sesi ini sebagai topik umum?',
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(ctx).pop(false),
              child: const Text('Batal'),
            ),
            FilledButton(
              onPressed: () => Navigator.of(ctx).pop(true),
              child: const Text('Tandai'),
            ),
          ],
        ),
      );
      if (confirmed != true || !mounted) return;
    }

    final err = await ref
        .read(mitraChatProvider.notifier)
        .flipTopic(widget.sessionId, toValue);

    if (!mounted) return;
    if (err != null) {
      final msg = err == 'SENSITIVITY_LATCHED'
          ? 'Sesi sudah ditandai sensitif dan tidak bisa dikembalikan.'
          : err == 'SESSION_NOT_ACTIVE'
              ? 'Sesi sudah berakhir.'
              : 'Gagal mengubah topik. Coba lagi.';
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            toValue == TopicSensitivity.sensitive
                ? 'Sesi ditandai sensitif'
                : 'Sesi ditandai topik umum',
          ),
          duration: const Duration(seconds: 2),
        ),
      );
    }
  }
}

/// Owns the chat-body subtree. Watches `mitraChatProvider` and
/// `mitraExtensionProvider` — so a WS message / typing / status / extension
/// update rebuilds *this* widget only, not the parent Scaffold or AppBar.
/// Entry-banner dismiss state moved here from the parent so its setState
/// doesn't propagate back up either.
class _MitraChatBodyContent extends ConsumerStatefulWidget {
  final String sessionId;
  final String customerName;
  final TextEditingController messageController;
  final TextEditingController goodbyeController;
  final ScrollController scrollController;
  final VoidCallback onSend;
  final ValueChanged<String> onTextChanged;

  const _MitraChatBodyContent({
    required this.sessionId,
    required this.customerName,
    required this.messageController,
    required this.goodbyeController,
    required this.scrollController,
    required this.onSend,
    required this.onTextChanged,
  });

  @override
  ConsumerState<_MitraChatBodyContent> createState() => _MitraChatBodyContentState();
}

class _MitraChatBodyContentState extends ConsumerState<_MitraChatBodyContent> {
  // Phase 4 ESP topic display labels. Mirrors the customer-side `EspTopic`
  // enum's `label` property — we only need to read these here, not write.
  static const Map<String, String> _espTopicLabels = {
    'relationship': 'Hubungan',
    'family': 'Keluarga',
    'work': 'Pekerjaan',
    'study': 'Sekolah / Kuliah',
    'finance': 'Keuangan',
    'health': 'Kesehatan',
    'friendship': 'Pertemanan',
    'self_worth': 'Self-worth',
    'anxiety': 'Kecemasan',
    'loneliness': 'Kesepian',
    'grief': 'Kehilangan',
    'identity': 'Identitas',
  };

  @override
  Widget build(BuildContext context) {
    final chatState = ref.watch(mitraChatProvider);
    final extState = ref.watch(mitraExtensionProvider);
    final isSensitive = chatState is MitraChatConnectedData &&
        chatState.topicSensitivity == TopicSensitivity.sensitive;

    return Column(
      children: [
        if (isSensitive) _buildSensitivityHeader(),
        Expanded(child: _buildBody(chatState, extState)),
      ],
    );
  }

  Widget _buildSensitivityHeader() {
    const theme = SensitivityTheme.sensitive;
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
      color: theme.badgeBg,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.warning_amber_rounded, size: 16, color: theme.badgeFg),
          const SizedBox(width: 6),
          Text(
            'Topik sensitif',
            style: TextStyle(
              color: theme.badgeFg,
              fontSize: 12,
              fontWeight: FontWeight.w600,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBody(MitraChatData chatState, ExtensionData extState) {
    if (chatState is MitraChatConnectingData) {
      return const Center(child: CircularProgressIndicator());
    }
    if (chatState is MitraChatErrorData) {
      return Center(child: Text(chatState.message));
    }
    if (chatState is MitraChatConnectedData) {
      return _buildChatBody(chatState, extState);
    }
    return const SizedBox.shrink();
  }

  Widget _buildChatBody(MitraChatConnectedData state, ExtensionData extState) {
    // Extension request from customer
    if (state.extensionRequest != null) {
      return _buildExtensionView(state.extensionRequest!, extState);
    }

    // Goodbye view — suppress if mitra has already submitted their goodbye;
    // session can stay in `closing` while waiting for the customer to submit
    // theirs or for the 5-min grace timer to auto-complete.
    final showGoodbye = !state.goodbyeSubmitted &&
        (state.sessionClosing || extState is ExtensionShowGoodbyeData || extState is ExtensionSubmittingData);
    if (showGoodbye) {
      return _buildGoodbyeView(extState, state.customerDisplayName);
    }

    if (state.sessionClosing && state.goodbyeSubmitted) {
      return _buildAwaitingCustomerGoodbyeView(state);
    }

    // Background: flat cream (HaloTokens.bg) per Figma `BestieChatV5`. Drop
    // the previous `chat_pattern.png` wallpaper layer. Sensitivity tint still
    // applies for the sensitive case — it's a working feature; for regular
    // sessions we use the flat brand bg so the cream/Figma look comes through.
    final isSensitive = state.topicSensitivity == TopicSensitivity.sensitive;
    final bg = isSensitive
        ? SensitivityTheme.sensitive.bgTint
        : HaloTokens.bg;
    // Build list payload: leading "sesi dimulai" system pill, optional ESP
    // topic chip row, then the message bubbles. Single source of truth for
    // index → item resolution so the ListView builder stays simple.
    final hasTopics = state.topics.isNotEmpty;
    // [0]=system pill, [1]=topics (optional), then messages.
    final leadingCount = 1 + (hasTopics ? 1 : 0);

    return Container(
      color: bg,
      child: Column(
        children: [
          // Session-ended banner (mirrors `BestieChatV5` ended-state notice in
          // figma-bestie/.../v5.jsx:294). Pinned directly under the header so
          // it's visible regardless of scroll position.
          if (state.sessionExpired) _buildSessionEndedBanner(),
          Expanded(
            child: ListView.builder(
              controller: widget.scrollController,
              padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
              itemCount: state.messages.length + leadingCount,
              itemBuilder: (context, index) {
                if (index == 0) return _buildSystemPill('sesi dimulai');
                if (hasTopics && index == 1) {
                  return _buildTopicChipsRow(state.topics);
                }
                final msg = state.messages[index - leadingCount];
                final isMe = msg.senderType == UserType.mitra;
                return _buildMessageBubble(msg, isMe);
              },
            ),
          ),
          // Typing indicator
          if (state.isOtherTyping)
            const Padding(
              padding: EdgeInsets.fromLTRB(16, 0, 16, 6),
              child: Align(
                alignment: Alignment.centerLeft,
                child: Text(
                  'Customer sedang mengetik...',
                  style: TextStyle(color: HaloTokens.inkMuted, fontSize: 12),
                ),
              ),
            ),
          // Input bar — softer disabled-state notice once the timer hits
          // zero. Tunggu klien perpanjang / tutup obrolan; we don't accept
          // new mitra messages on an expired session.
          if (state.sessionExpired)
            _buildEndedInputNotice()
          else
            _buildInputBar(),
        ],
      ),
    );
  }

  /// Centered system-message pill. Replaces the legacy bracketed entry
  /// banners. Mirrors `BestieChatV5` (figma-bestie/.../v5.jsx:255).
  Widget _buildSystemPill(String text) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Align(
        alignment: Alignment.center,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
          decoration: const BoxDecoration(
            color: HaloTokens.brandSofter,
            borderRadius: HaloRadius.pill,
          ),
          child: Text(
            text,
            style: const TextStyle(
              fontSize: 11,
              fontWeight: FontWeight.w500,
              color: HaloTokens.brand,
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildTopicChipsRow(List<String> topics) {
    return Container(
      margin: const EdgeInsets.only(bottom: 12),
      child: Wrap(
        spacing: 6,
        runSpacing: 6,
        children: topics.map((value) {
          final label = _espTopicLabels[value] ?? value;
          return Container(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
            decoration: BoxDecoration(
              color: HaloTokens.surface,
              borderRadius: HaloRadius.pill,
              border: Border.all(color: HaloTokens.border),
            ),
            child: Text(
              label,
              style: const TextStyle(
                fontSize: 12,
                color: HaloTokens.brandDark,
                fontWeight: FontWeight.w500,
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  Widget _buildSessionEndedBanner() {
    const bg = Color(0xFFFFE5E5);
    const border = Color(0xFFF5B5B5);
    const ink = Color(0xFF7A2828);
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
      decoration: const BoxDecoration(
        color: bg,
        border: Border(bottom: BorderSide(color: border)),
      ),
      child: const Row(
        children: [
          Icon(Icons.access_time, color: ink, size: 18),
          SizedBox(width: 10),
          Expanded(
            child: Text.rich(
              TextSpan(
                children: [
                  TextSpan(
                    text: 'Durasi sesi habis. ',
                    style: TextStyle(fontWeight: FontWeight.w700, color: ink),
                  ),
                  TextSpan(
                    text: 'Tunggu klien perpanjang atau tutup obrolan.',
                    style: TextStyle(color: ink),
                  ),
                ],
              ),
              style: TextStyle(fontSize: 12.5, height: 1.4),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildEndedInputNotice() {
    return SafeArea(
      child: Container(
        width: double.infinity,
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        decoration: const BoxDecoration(
          color: Colors.white,
          border: Border(top: BorderSide(color: HaloTokens.border)),
        ),
        child: const Text(
          'Sesi sudah berakhir 💛',
          textAlign: TextAlign.center,
          style: TextStyle(
            fontSize: 13,
            color: HaloTokens.inkSoft,
            fontWeight: FontWeight.w500,
          ),
        ),
      ),
    );
  }

  // TODO(stage7): reply-quote bubble polish. Figma `BestieChatV5` renders a
  // quote block above the bubble text when a message replies to another
  // (2px left accent stripe, original sender name, truncated original text).
  // Out of scope for Stage 6 — the `MitraChatMessage` model in
  // `core/chat/mitra_chat_notifier.dart` does not carry a `replyTo` payload
  // and the backend WS frames don't ship one. Adding it requires changes to
  // backend message schema + this notifier + ChatService. Until then there
  // are no reply-to messages to render. Corner-radius logic below already
  // accepts the future `replyTo` case (top corner becomes 4 when replying).
  Widget _buildMessageBubble(MitraChatMessage msg, bool isMe) {
    // Tail corner: 4 on the side facing the sender's edge (bottom-right for
    // me, bottom-left for them). In stage7 the top corner will also become
    // 4 when this message replies to another — see TODO(stage7) above.
    final radius = BorderRadius.only(
      topLeft: const Radius.circular(16),
      topRight: const Radius.circular(16),
      bottomLeft: Radius.circular(isMe ? 16 : 4),
      bottomRight: Radius.circular(isMe ? 4 : 16),
    );
    final maxW = MediaQuery.of(context).size.width * 0.78;

    return Align(
      alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
      child: Padding(
        padding: const EdgeInsets.only(bottom: 8),
        child: ConstrainedBox(
          constraints: BoxConstraints(maxWidth: maxW),
          child: Column(
            crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
            children: [
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
                decoration: BoxDecoration(
                  gradient: isMe ? _kMitraBubbleGradient : null,
                  color: isMe ? null : HaloTokens.surface,
                  border: isMe ? null : Border.all(color: HaloTokens.border),
                  borderRadius: radius,
                ),
                child: Text(
                  msg.content,
                  style: TextStyle(
                    fontSize: 13.5,
                    height: 1.45,
                    color: isMe ? Colors.white : HaloTokens.ink,
                  ),
                ),
              ),
              const SizedBox(height: 2),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    '${msg.createdAt.hour.toString().padLeft(2, '0')}:${msg.createdAt.minute.toString().padLeft(2, '0')}',
                    style: const TextStyle(fontSize: 10, color: HaloTokens.inkMuted),
                  ),
                  if (isMe) ...[
                    const SizedBox(width: 4),
                    _buildStatusIcon(msg.status),
                  ],
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildStatusIcon(String status) {
    switch (status) {
      case 'sending':
        return const Icon(Icons.access_time, size: 12, color: HaloTokens.inkMuted);
      case MessageStatus.sent:
        return const Icon(Icons.check, size: 12, color: HaloTokens.inkMuted);
      case MessageStatus.delivered:
        return const Icon(Icons.done_all, size: 12, color: HaloTokens.inkMuted);
      case MessageStatus.read:
        return const Icon(Icons.done_all, size: 12, color: HaloTokens.brand);
      default:
        return const SizedBox.shrink();
    }
  }

  Widget _buildInputBar() {
    // Mirrors `BestieChatV5` (figma-bestie/.../v5.jsx:305): cream-bg pill text
    // field with a round pink send button. SafeArea takes care of the home-
    // indicator inset; the JSX uses a hardcoded 30px bottom pad — we let
    // SafeArea handle it instead so devices without a home indicator don't
    // get a giant gap.
    return Container(
      decoration: const BoxDecoration(
        color: HaloTokens.surface,
        border: Border(top: BorderSide(color: HaloTokens.border)),
      ),
      child: SafeArea(
        top: false,
        child: Padding(
          padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
          // Both children rendered inside a Container(height: 44) so their
          // visible heights match exactly. Earlier attempt used
          // OutlineInputBorder on the TextField directly, but OutlineInputBorder
          // sizes to text content (not the parent SizedBox), so the pill came
          // out at ~29dp while the round button was 44dp — visual centers
          // drifted ~43px apart. This pattern is bulletproof: a Container
          // draws the pill outline + bg, a borderless centered TextField sits
          // inside, and the send button is the same explicit 44dp Container.
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Expanded(
                // Pattern lifted from client_app/chat_screen.dart::_InputBar
                // — same shape, same vertical-centering trick. Key bits:
                //   - Material + StadiumBorder = pill outline + proper focus
                //     clipping
                //   - Center wrapper centers the (isCollapsed) TextField
                //   - `constraints: BoxConstraints()` overrides the app-wide
                //     InputDecorationTheme min-height (set in halo_theme for
                //     form fields). Without it, the field claims ~48dp and
                //     refuses to collapse — textAlignVertical becomes a
                //     no-op against the parent's 44dp height.
                child: SizedBox(
                  height: 44,
                  child: Material(
                    color: HaloTokens.surface,
                    shape: const StadiumBorder(
                      side: BorderSide(color: HaloTokens.border),
                    ),
                    clipBehavior: Clip.antiAlias,
                    child: Center(
                      child: TextField(
                        controller: widget.messageController,
                        onChanged: widget.onTextChanged,
                        textInputAction: TextInputAction.send,
                        onSubmitted: (_) => widget.onSend(),
                        maxLines: 1,
                        textAlignVertical: TextAlignVertical.center,
                        style: const TextStyle(
                          fontFamily: HaloTokens.fontBody,
                          fontSize: 13.5,
                          color: HaloTokens.ink,
                        ),
                        decoration: const InputDecoration(
                          filled: false,
                          fillColor: Colors.transparent,
                          border: InputBorder.none,
                          enabledBorder: InputBorder.none,
                          focusedBorder: InputBorder.none,
                          errorBorder: InputBorder.none,
                          focusedErrorBorder: InputBorder.none,
                          disabledBorder: InputBorder.none,
                          isCollapsed: true,
                          contentPadding: EdgeInsets.symmetric(horizontal: 16),
                          constraints: BoxConstraints(),
                          hintText: 'ketik balasan...',
                          hintStyle: TextStyle(
                            fontFamily: HaloTokens.fontBody,
                            fontSize: 13.5,
                            color: HaloTokens.inkMuted,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 8),
              Container(
                width: 44,
                height: 44,
                decoration: const BoxDecoration(
                  color: HaloTokens.brand,
                  shape: BoxShape.circle,
                ),
                child: Material(
                  color: Colors.transparent,
                  shape: const CircleBorder(),
                  child: InkWell(
                    customBorder: const CircleBorder(),
                    onTap: widget.onSend,
                    child: const Center(
                      child: Icon(Icons.arrow_upward, color: Colors.white, size: 18),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildExtensionView(Map<String, dynamic> request, ExtensionData extState) {
    final duration = request['duration_minutes'] as int?;
    final extensionId = request['extension_id'] as String?;
    final isResponding = extState is ExtensionRespondingData;
    final topic = TopicSensitivity.fromString(request['topic_sensitivity'] as String?);
    final isSensitive = topic == TopicSensitivity.sensitive;
    // Extensions auto-approve on mitra non-response (server-side, with connectivity
    // safeguards). Surface the configured timeout to the mitra so they know what
    // "no response" means in this card.
    final timeoutSeconds = request['timeout_seconds'] as int?;

    return Container(
      color: isSensitive ? SensitivityTheme.sensitive.bgTint : null,
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(32),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.timer, size: 64, color: Colors.orange),
              const SizedBox(height: 16),
              const Text('Permintaan Perpanjangan', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
              if (isSensitive) ...[
                const SizedBox(height: 8),
                SensitivityBadge(sensitivity: topic),
              ],
              const SizedBox(height: 8),
              Text('Customer ingin perpanjang $duration menit', textAlign: TextAlign.center),
              if (timeoutSeconds != null) ...[
                const SizedBox(height: 12),
                Text(
                  'Tidak menjawab dalam $timeoutSeconds detik = otomatis disetujui',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 13,
                    color: Colors.grey.shade700,
                    fontStyle: FontStyle.italic,
                  ),
                ),
              ],
              const SizedBox(height: 24),
              if (isResponding)
                const CircularProgressIndicator()
              else
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                      onPressed: extensionId == null ? null : () => ref.read(mitraExtensionProvider.notifier).respond(
                        widget.sessionId,
                        extensionId: extensionId,
                        accepted: true,
                      ),
                      child: const Text('Terima', style: TextStyle(color: Colors.white)),
                    ),
                    const SizedBox(width: 16),
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                      onPressed: extensionId == null ? null : () => ref.read(mitraExtensionProvider.notifier).respond(
                        widget.sessionId,
                        extensionId: extensionId,
                        accepted: false,
                      ),
                      child: const Text('Tolak', style: TextStyle(color: Colors.white)),
                    ),
                  ],
                ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildGoodbyeView(ExtensionData extState, String? customerDisplayName) {
    final name = (customerDisplayName != null && customerDisplayName.isNotEmpty)
        ? customerDisplayName
        : widget.customerName;
    return SingleChildScrollView(
      padding: const EdgeInsets.all(32),
      child: Column(
        children: [
          const SizedBox(height: 48),
          const Icon(Icons.waving_hand, size: 64, color: Colors.amber),
          const SizedBox(height: 16),
          const Text('Pesan Penutup', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text('Tuliskan pesan terakhirmu untuk $name', textAlign: TextAlign.center),
          const SizedBox(height: 24),
          TextField(
            controller: widget.goodbyeController,
            maxLines: 3,
            decoration: InputDecoration(
              hintText: 'Terima kasih sudah curhat...',
              border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
            ),
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: extState is ExtensionSubmittingData
                ? null
                : () {
                    final text = widget.goodbyeController.text.trim();
                    if (text.isNotEmpty) {
                      ref.read(mitraExtensionProvider.notifier).submitGoodbye(
                        widget.sessionId, text,
                      );
                    }
                  },
            child: extState is ExtensionSubmittingData
                ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
                : const Text('Kirim & Selesai'),
          ),
        ],
      ),
    );
  }

  Widget _buildAwaitingCustomerGoodbyeView(MitraChatConnectedData state) {
    final isSensitive = state.topicSensitivity == TopicSensitivity.sensitive;
    final bg = isSensitive
        ? SensitivityTheme.sensitive.bgTint
        : HaloTokens.bg;
    return Container(
      color: bg,
      child: Column(
        children: [
          Container(
            width: double.infinity,
            color: Colors.amber.shade100,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            child: Row(
              children: [
                Icon(Icons.hourglass_top, color: Colors.amber.shade900, size: 20),
                const SizedBox(width: 8),
                Expanded(
                  child: Text(
                    'Pesan penutupmu sudah terkirim. Menunggu user...',
                    style: TextStyle(color: Colors.amber.shade900, fontWeight: FontWeight.w600),
                  ),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              controller: widget.scrollController,
              padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
              itemCount: state.messages.length,
              itemBuilder: (context, index) {
                final msg = state.messages[index];
                final isMe = msg.senderType == UserType.mitra;
                return _buildMessageBubble(msg, isMe);
              },
            ),
          ),
        ],
      ),
    );
  }
}

/// Tiny AppBar action that watches only [mitraChatRemainingSecondsProvider].
/// Decoupling the timer from the chat state means a WS `session_tick` frame
/// rebuilds *only* this widget (a single Text), not the surrounding AppBar,
/// Scaffold body, message ListView, or input bar. This is the per-second
/// hotspot the wider chat-screen perf work targets.
///
/// Visual mirrors `BestieChatV5` (figma-bestie/.../v5.jsx):
///   - active  → amber pill "SISA WAKTU\nmm:ss" (#FFE8D9 bg / #FFCAA0 border)
///   - ended   → red pill   "SELESAI\n00:00"    (#FFE5E5 bg / #F5B5B5 border)
/// "Ended" here is `seconds <= 0`. The session-expired flag lives on
/// `mitraChatProvider` but reading it here would re-couple this widget to
/// the chat state and defeat the per-second perf isolation — so we use the
/// timer reaching zero as a proxy. The red banner below the header uses the
/// authoritative `sessionExpired` flag.
class _MitraChatTimerAction extends ConsumerWidget {
  const _MitraChatTimerAction();

  static const _activeBg = Color(0xFFFFE8D9);
  static const _activeBorder = Color(0xFFFFCAA0);
  static const _activeInk = Color(0xFF7A3E08);
  static const _endedBg = Color(0xFFFFE5E5);
  static const _endedBorder = Color(0xFFF5B5B5);
  static const _endedInk = Color(0xFF7A2828);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final seconds = ref.watch(mitraChatRemainingSecondsProvider);

    // When the first session_timer frame hasn't arrived yet (`seconds == null`)
    // show a placeholder pill — matches the Figma which always renders the
    // pill — instead of disappearing. Treat null as "loading", not "ended".
    final loading = seconds == null;
    final ended = !loading && seconds <= 0;
    final mm = loading ? '--' : (seconds ~/ 60).clamp(0, 99).toString().padLeft(2, '0');
    final ss = loading ? '--' : (seconds % 60).clamp(0, 59).toString().padLeft(2, '0');
    final label = ended ? 'SELESAI' : 'SISA WAKTU';
    final value = ended ? '00:00' : '$mm:$ss';
    final bg = ended ? _endedBg : _activeBg;
    final border = ended ? _endedBorder : _activeBorder;
    final ink = ended ? _endedInk : _activeInk;

    return Padding(
      padding: const EdgeInsets.only(right: 12),
      child: Center(
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
          decoration: BoxDecoration(
            color: bg,
            borderRadius: HaloRadius.sm,
            border: Border.all(color: border),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                label,
                style: TextStyle(
                  fontSize: 8,
                  fontWeight: FontWeight.w700,
                  letterSpacing: 0.5,
                  color: ink,
                ),
              ),
              Text(
                value,
                style: TextStyle(
                  fontSize: 13,
                  fontWeight: FontWeight.w700,
                  color: ink,
                  fontFeatures: const [FontFeature.tabularFigures()],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
