import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:matrix/matrix.dart';
import 'package:pyramid/core/app_state.dart';
import 'package:pyramid/core/matrix_client.dart';
import 'package:pyramid/core/theme.dart';
import 'package:pyramid/widgets/mxc_image.dart';

// ─── State ────────────────────────────────────────────────────────────────────

enum _Filter { all, media, people }

class _SearchState {
  final bool loading;
  final List<Event> messageResults;
  final List<Room> roomResults;
  final String? error;

  const _SearchState({
    this.loading = false,
    this.messageResults = const [],
    this.roomResults = const [],
    this.error,
  });

  _SearchState copyWith({
    bool? loading,
    List<Event>? messageResults,
    List<Room>? roomResults,
    String? error,
  }) => _SearchState(
    loading: loading ?? this.loading,
    messageResults: messageResults ?? this.messageResults,
    roomResults: roomResults ?? this.roomResults,
    error: error,
  );
}

// ─── Widget ───────────────────────────────────────────────────────────────────

class SearchModal extends ConsumerStatefulWidget {
  final VoidCallback onClose;
  const SearchModal({super.key, required this.onClose});

  @override
  ConsumerState<SearchModal> createState() => _SearchModalState();
}

class _SearchModalState extends ConsumerState<SearchModal> {
  final _ctrl = TextEditingController();
  final _focusNode = FocusNode();
  _Filter _filter = _Filter.all;
  String _query = '';
  int _selected = 0;
  _SearchState _state = const _SearchState();
  Timer? _debounce;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus());
  }

  @override
  void dispose() {
    _debounce?.cancel();
    _ctrl.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _onQueryChanged(String q) {
    setState(() {
      _query = q;
      _selected = 0;
    });
    _debounce?.cancel();
    if (q.trim().isEmpty) {
      setState(() => _state = const _SearchState());
      return;
    }
    _debounce = Timer(const Duration(milliseconds: 400), () => _runSearch(q.trim()));
  }

  Future<void> _runSearch(String q) async {
    final client = ref.read(matrixClientProvider).valueOrNull;
    if (client == null) return;
    setState(() => _state = _state.copyWith(loading: true, error: null));

    // Room/people filter: purely local
    final allRooms = client.rooms.where((r) => !r.isSpace).toList();
    final roomResults = _filter == _Filter.media
        ? <Room>[]
        : allRooms
            .where((r) {
              if (_filter == _Filter.people && !r.isDirectChat) return false;
              if (_filter == _Filter.all && r.isDirectChat) return false;
              return r.getLocalizedDisplayname().toLowerCase().contains(q.toLowerCase());
            })
            .take(5)
            .toList();

    // Message/media search: LOCAL over decrypted events. The server-side
    // /search API can't see E2EE message content (only ciphertext), so it
    // returns nothing for encrypted rooms — we search the local database
    // (which holds decrypted events) instead, like Element does.
    List<Event> messageResults = [];
    if (_filter != _Filter.people) {
      try {
        messageResults = await _localMessageSearch(client, q);
      } catch (e) {
        setState(() => _state = _state.copyWith(
          loading: false,
          error: 'Suche fehlgeschlagen: $e',
        ));
        return;
      }
    }

    if (mounted) {
      setState(() => _state = _SearchState(
        loading: false,
        messageResults: messageResults,
        roomResults: roomResults,
      ));
    }
  }

  // Scans the local (decrypted) event database across all rooms for messages
  // whose body matches the query. For the media filter, only media messages
  // whose filename/caption matches are returned.
  Future<List<Event>> _localMessageSearch(Client client, String q) async {
    final db = client.database;
    final lower = q.toLowerCase();
    final media = _filter == _Filter.media;
    const mediaTypes = {'m.image', 'm.video', 'm.file', 'm.audio'};
    const perRoomScanCap = 1500;
    const totalCap = 60;
    const chunk = 500;

    final results = <Event>[];
    for (final room in client.rooms.where((r) => !r.isSpace)) {
      var start = 0;
      var scanned = 0;
      try {
        while (scanned < perRoomScanCap) {
          final events = await db.getEventList(room, start: start, limit: chunk);
          if (events.isEmpty) break;
          for (final e in events) {
            if (e.type != EventTypes.Message || e.redacted) continue;
            final msgtype = e.content.tryGet<String>('msgtype') ?? '';
            if (media && !mediaTypes.contains(msgtype)) continue;
            if (!e.body.toLowerCase().contains(lower)) continue;
            results.add(e);
          }
          start += chunk;
          scanned += events.length;
          if (events.length < chunk || results.length >= totalCap) break;
        }
      } catch (_) {}
      if (results.length >= totalCap) break;
    }

    results.sort((a, b) => b.originServerTs.compareTo(a.originServerTs));
    return results.take(totalCap).toList();
  }

  void _openRoom(String roomId, {String? eventId}) {
    widget.onClose();
    ref.read(activeRoomIdProvider.notifier).state = roomId;
    ref.read(viewModeProvider.notifier).state = ViewMode.chat;
    if (eventId != null) {
      ref.read(pendingJumpEventProvider.notifier).state = eventId;
    }
  }

  int get _totalResults =>
      _state.roomResults.length + _state.messageResults.length;

  @override
  Widget build(BuildContext context) {
    final pt = PyramidTheme.of(context);
    final client = ref.watch(matrixClientProvider).valueOrNull;

    final filters = [
      (_Filter.all, 'Alle', Icons.auto_awesome_rounded),
      (_Filter.media, 'Medien', Icons.image_rounded),
      (_Filter.people, 'Personen & Räume', Icons.group_rounded),
    ];

    return KeyboardListener(
      focusNode: FocusNode(),
      onKeyEvent: (e) {
        if (e is! KeyDownEvent) return;
        if (e.logicalKey == LogicalKeyboardKey.escape) widget.onClose();
        if (e.logicalKey == LogicalKeyboardKey.arrowDown) {
          setState(() => _selected = (_selected + 1) % _totalResults.clamp(1, 999));
        }
        if (e.logicalKey == LogicalKeyboardKey.arrowUp) {
          setState(() => _selected = (_selected - 1).clamp(0, _totalResults - 1));
        }
        if (e.logicalKey == LogicalKeyboardKey.enter) {
          _activateSelected(client);
        }
      },
      child: GestureDetector(
        onTap: widget.onClose,
        child: Container(
          color: Colors.black.withAlpha(150),
          alignment: Alignment.topCenter,
          padding: const EdgeInsets.only(top: 80),
          child: GestureDetector(
            onTap: () {},
            child: Container(
              width: 640,
              constraints: const BoxConstraints(maxHeight: 560),
              decoration: BoxDecoration(
                color: pt.bg1,
                borderRadius: BorderRadius.circular(pt.rXl),
                border: Border.all(color: pt.border),
                boxShadow: [
                  BoxShadow(color: Colors.black.withAlpha(180), blurRadius: 60, offset: const Offset(0, 20)),
                ],
              ),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  // Input
                  _SearchBar(ctrl: _ctrl, focusNode: _focusNode, onChanged: _onQueryChanged, pt: pt),
                  // Filters
                  _FilterRow(
                    filters: filters,
                    active: _filter,
                    pt: pt,
                    onSelect: (f) {
                      setState(() { _filter = f; _selected = 0; });
                      if (_query.trim().isNotEmpty) _runSearch(_query.trim());
                    },
                  ),
                  // Results
                  Flexible(child: _ResultsPane(
                    state: _state,
                    query: _query,
                    filter: _filter,
                    selected: _selected,
                    pt: pt,
                    client: client,
                    onOpenRoom: _openRoom,
                  )),
                  // Footer
                  _Footer(
                    count: _totalResults,
                    loading: _state.loading,
                    pt: pt,
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  void _activateSelected(Client? client) {
    final roomCount = _state.roomResults.length;
    if (_selected < roomCount) {
      _openRoom(_state.roomResults[_selected].id);
    } else {
      final idx = _selected - roomCount;
      if (idx < _state.messageResults.length) {
        final e = _state.messageResults[idx];
        final rid = e.roomId;
        if (rid != null) _openRoom(rid, eventId: e.eventId);
      }
    }
  }
}

// ─── Search bar ───────────────────────────────────────────────────────────────

class _SearchBar extends StatelessWidget {
  final TextEditingController ctrl;
  final FocusNode focusNode;
  final ValueChanged<String> onChanged;
  final PyramidTheme pt;
  const _SearchBar({required this.ctrl, required this.focusNode, required this.onChanged, required this.pt});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      decoration: BoxDecoration(border: Border(bottom: BorderSide(color: pt.border))),
      child: Row(
        children: [
          Icon(Icons.search_rounded, size: 20, color: pt.fgDim),
          const SizedBox(width: 10),
          Expanded(
            child: TextField(
              controller: ctrl,
              focusNode: focusNode,
              style: TextStyle(color: pt.fg, fontSize: 15),
              cursorColor: pt.accent,
              decoration: InputDecoration(
                hintText: 'Nachrichten, Medien, Personen, Räume suchen…',
                hintStyle: TextStyle(color: pt.fgDim, fontSize: 15),
                border: InputBorder.none,
                contentPadding: EdgeInsets.zero,
                isDense: true,
              ),
              onChanged: onChanged,
            ),
          ),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
            decoration: BoxDecoration(
              color: pt.bg3,
              borderRadius: BorderRadius.circular(4),
              border: Border.all(color: pt.border),
            ),
            child: Text('esc', style: TextStyle(color: pt.fgMuted, fontSize: 11)),
          ),
        ],
      ),
    );
  }
}

// ─── Filter row ───────────────────────────────────────────────────────────────

class _FilterRow extends StatelessWidget {
  final List<(_Filter, String, IconData)> filters;
  final _Filter active;
  final PyramidTheme pt;
  final ValueChanged<_Filter> onSelect;
  const _FilterRow({required this.filters, required this.active, required this.pt, required this.onSelect});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 8),
      decoration: BoxDecoration(border: Border(bottom: BorderSide(color: pt.border))),
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 12),
        child: Row(
          children: filters.map((f) => Padding(
            padding: const EdgeInsets.only(right: 6),
            child: _Chip(label: f.$2, icon: f.$3, active: active == f.$1, pt: pt, onTap: () => onSelect(f.$1)),
          )).toList(),
        ),
      ),
    );
  }
}

// ─── Results pane ─────────────────────────────────────────────────────────────

class _ResultsPane extends StatelessWidget {
  final _SearchState state;
  final String query;
  final _Filter filter;
  final int selected;
  final PyramidTheme pt;
  final Client? client;
  final void Function(String roomId, {String? eventId}) onOpenRoom;

  const _ResultsPane({
    required this.state,
    required this.query,
    required this.filter,
    required this.selected,
    required this.pt,
    required this.client,
    required this.onOpenRoom,
  });

  @override
  Widget build(BuildContext context) {
    if (query.isEmpty) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(32),
          child: Column(mainAxisSize: MainAxisSize.min, children: [
            Icon(Icons.search_rounded, size: 36, color: pt.fgDim),
            const SizedBox(height: 10),
            Text('Tippe um zu suchen', style: TextStyle(color: pt.fgMuted, fontSize: 14)),
          ]),
        ),
      );
    }

    if (state.loading) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(32),
          child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: pt.accent)),
        ),
      );
    }

    if (state.error != null) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(32),
          child: Text(state.error!, style: TextStyle(color: pt.danger, fontSize: 13)),
        ),
      );
    }

    final hasRooms = state.roomResults.isNotEmpty;
    final hasMessages = state.messageResults.isNotEmpty;

    if (!hasRooms && !hasMessages) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(32),
          child: Text('Keine Ergebnisse für "$query"', style: TextStyle(color: pt.fgMuted, fontSize: 14)),
        ),
      );
    }

    int itemIndex = 0;
    final items = <Widget>[];

    if (hasRooms) {
      items.add(_SectionHeader(label: filter == _Filter.people ? 'Personen & Räume' : 'Räume', pt: pt));
      for (final room in state.roomResults) {
        final idx = itemIndex++;
        items.add(_RoomItem(room: room, selected: selected == idx, pt: pt, onTap: () => onOpenRoom(room.id)));
      }
    }

    if (hasMessages) {
      items.add(_SectionHeader(label: filter == _Filter.media ? 'Medien' : 'Nachrichten', pt: pt));
      for (final event in state.messageResults) {
        final idx = itemIndex++;
        final room = client?.getRoomById(event.roomId ?? '') ?? event.room;
        items.add(_MessageItem(
          event: event,
          room: room,
          selected: selected == idx,
          pt: pt,
          onTap: () => onOpenRoom(event.roomId ?? '', eventId: event.eventId),
        ));
      }
    }

    return ListView(
      padding: const EdgeInsets.symmetric(vertical: 4),
      shrinkWrap: true,
      children: items,
    );
  }
}

// ─── Section header ───────────────────────────────────────────────────────────

class _SectionHeader extends StatelessWidget {
  final String label;
  final PyramidTheme pt;
  const _SectionHeader({required this.label, required this.pt});
  @override
  Widget build(BuildContext context) => Padding(
    padding: const EdgeInsets.fromLTRB(14, 10, 14, 4),
    child: Text(label.toUpperCase(), style: TextStyle(color: pt.fgDim, fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 0.8)),
  );
}

// ─── Room result item ─────────────────────────────────────────────────────────

class _RoomItem extends StatefulWidget {
  final Room room;
  final bool selected;
  final PyramidTheme pt;
  final VoidCallback onTap;
  const _RoomItem({required this.room, required this.selected, required this.pt, required this.onTap});
  @override
  State<_RoomItem> createState() => _RoomItemState();
}

class _RoomItemState extends State<_RoomItem> {
  bool _hovered = false;

  Color _colorForName(String n) {
    const c = [Color(0xFF06B6D4), Color(0xFFA855F7), Color(0xFFFF6B9D), Color(0xFF10B981), Color(0xFFF59E0B), Color(0xFF3B82F6)];
    return c[n.codeUnits.fold(0, (a, b) => a + b) % c.length];
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    final room = widget.room;
    final name = room.getLocalizedDisplayname();
    final avatarUrl = room.avatar;
    final isDm = room.isDirectChat;

    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => setState(() => _hovered = true),
      onExit: (_) => setState(() => _hovered = false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 100),
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
          margin: const EdgeInsets.symmetric(horizontal: 6),
          decoration: BoxDecoration(
            color: widget.selected || _hovered ? pt.bgHover : Colors.transparent,
            borderRadius: BorderRadius.circular(pt.rSm),
          ),
          child: Row(
            children: [
              SizedBox(width: 32, height: 32,
                child: avatarUrl != null
                  ? ClipRRect(
                      borderRadius: BorderRadius.circular(isDm ? 16 : pt.rSm),
                      child: MxcImage(mxcUri: avatarUrl.toString(), client: room.client, width: 32, height: 32),
                    )
                  : Container(
                      decoration: BoxDecoration(
                        color: _colorForName(name),
                        borderRadius: BorderRadius.circular(isDm ? 16 : pt.rSm),
                      ),
                      child: Center(child: Text(name.isEmpty ? '?' : name[0].toUpperCase(),
                        style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14))),
                    ),
              ),
              const SizedBox(width: 10),
              Expanded(child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(children: [
                    Icon(isDm ? Icons.person_rounded : Icons.tag_rounded, size: 11, color: pt.fgDim),
                    const SizedBox(width: 3),
                    Expanded(child: Text(name, style: TextStyle(color: pt.fg, fontSize: 13, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis)),
                  ]),
                  if (room.lastEvent != null)
                    Text(room.lastEvent!.body, style: TextStyle(color: pt.fgMuted, fontSize: 12), overflow: TextOverflow.ellipsis, maxLines: 1),
                ],
              )),
              if (room.notificationCount > 0)
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                  decoration: BoxDecoration(color: pt.accent, borderRadius: BorderRadius.circular(999)),
                  child: Text('${room.notificationCount}', style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600)),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

// ─── Message result item ──────────────────────────────────────────────────────

class _MessageItem extends StatefulWidget {
  final Event event;
  final Room? room;
  final bool selected;
  final PyramidTheme pt;
  final VoidCallback onTap;
  const _MessageItem({required this.event, required this.room, required this.selected, required this.pt, required this.onTap});
  @override
  State<_MessageItem> createState() => _MessageItemState();
}

class _MessageItemState extends State<_MessageItem> {
  bool _hovered = false;

  String _senderName() {
    final event = widget.event;
    final room = widget.room;
    if (room != null) {
      final member = room.unsafeGetUserFromMemoryOrFallback(event.senderId);
      return member.displayName ?? event.senderId.split(':').first.replaceFirst('@', '');
    }
    return event.senderId.split(':').first.replaceFirst('@', '');
  }

  String _timeLabel() {
    final ts = widget.event.originServerTs;
    final now = DateTime.now();
    final diff = now.difference(ts);
    if (diff.inDays == 0) return '${ts.hour.toString().padLeft(2, '0')}:${ts.minute.toString().padLeft(2, '0')}';
    if (diff.inDays < 7) return _weekday(ts.weekday);
    return '${ts.day}.${ts.month}.${ts.year}';
  }

  String _weekday(int d) => ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'][d - 1];

  String _msgBody() {
    final content = widget.event.content;
    final msgtype = content.tryGet<String>('msgtype') ?? '';
    if (msgtype == 'm.image') return '📷 Bild';
    if (msgtype == 'm.video') return '🎬 Video';
    if (msgtype == 'm.audio') return '🎵 Audio';
    if (msgtype == 'm.file') return '📎 ${content.tryGet<String>('body') ?? 'Datei'}';
    return content.tryGet<String>('body') ?? '';
  }

  IconData _roomIcon() {
    final room = widget.room;
    if (room == null) return Icons.tag_rounded;
    return room.isDirectChat ? Icons.person_rounded : Icons.tag_rounded;
  }

  bool get _isVisualMedia {
    final t = widget.event.content.tryGet<String>('msgtype') ?? '';
    return t == 'm.image' || t == 'm.video';
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    final roomName = widget.room?.getLocalizedDisplayname() ?? widget.event.roomId ?? '';

    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => setState(() => _hovered = true),
      onExit: (_) => setState(() => _hovered = false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 100),
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
          margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
          decoration: BoxDecoration(
            color: widget.selected || _hovered ? pt.bgHover : Colors.transparent,
            borderRadius: BorderRadius.circular(pt.rSm),
          ),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              if (_isVisualMedia)
                Padding(
                  padding: const EdgeInsets.only(top: 1, right: 8),
                  child: _SearchThumb(event: widget.event, pt: pt),
                )
              else ...[
                Padding(
                  padding: const EdgeInsets.only(top: 2),
                  child: Icon(_roomIcon(), size: 14, color: pt.fgDim),
                ),
                const SizedBox(width: 8),
              ],
              Expanded(child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(children: [
                    Expanded(child: Text(roomName, style: TextStyle(color: pt.fgMuted, fontSize: 11), overflow: TextOverflow.ellipsis)),
                    Text(_timeLabel(), style: TextStyle(color: pt.fgDim, fontSize: 11)),
                  ]),
                  const SizedBox(height: 1),
                  RichText(
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    text: TextSpan(children: [
                      TextSpan(text: '${_senderName()}: ', style: TextStyle(color: pt.fg, fontSize: 13, fontWeight: FontWeight.w600)),
                      TextSpan(text: _msgBody(), style: TextStyle(color: pt.fgMuted, fontSize: 13)),
                    ]),
                  ),
                ],
              )),
            ],
          ),
        ),
      ),
    );
  }
}

// ─── Search media thumbnail ───────────────────────────────────────────────────
// Loads (and decrypts, if needed) a small thumbnail for image/video results.

class _SearchThumb extends StatefulWidget {
  final Event event;
  final PyramidTheme pt;
  const _SearchThumb({required this.event, required this.pt});
  @override
  State<_SearchThumb> createState() => _SearchThumbState();
}

class _SearchThumbState extends State<_SearchThumb> {
  static final Map<String, Uint8List?> _cache = {};
  Uint8List? _bytes;

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

  Future<void> _load() async {
    final id = widget.event.eventId;
    if (_cache.containsKey(id)) {
      setState(() => _bytes = _cache[id]);
      return;
    }
    try {
      final file = await widget.event
          .downloadAndDecryptAttachment(getThumbnail: true)
          .timeout(const Duration(seconds: 10));
      _cache[id] = file.bytes;
      if (mounted) setState(() => _bytes = file.bytes);
    } catch (_) {
      _cache[id] = null;
    }
  }

  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    const size = 38.0;
    return ClipRRect(
      borderRadius: BorderRadius.circular(pt.rSm),
      child: SizedBox(
        width: size,
        height: size,
        child: _bytes != null
            ? Image.memory(_bytes!, fit: BoxFit.cover,
                errorBuilder: (_, _, _) => _placeholder(pt))
            : _placeholder(pt),
      ),
    );
  }

  Widget _placeholder(PyramidTheme pt) => Container(
        color: pt.bg3,
        child: Icon(
          (widget.event.content.tryGet<String>('msgtype') ?? '') == 'm.video'
              ? Icons.videocam_rounded
              : Icons.image_rounded,
          size: 18,
          color: pt.fgDim,
        ),
      );
}

// ─── Shared chips / footer ────────────────────────────────────────────────────

class _Chip extends StatefulWidget {
  final String label;
  final IconData icon;
  final bool active;
  final PyramidTheme pt;
  final VoidCallback onTap;
  const _Chip({required this.label, required this.icon, required this.active, required this.pt, required this.onTap});
  @override
  State<_Chip> createState() => _ChipState();
}

class _ChipState extends State<_Chip> {
  bool _h = false;
  @override
  Widget build(BuildContext context) {
    final pt = widget.pt;
    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => setState(() => _h = true),
      onExit: (_) => setState(() => _h = false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 150),
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
          decoration: BoxDecoration(
            color: widget.active ? pt.accentSoft : _h ? pt.bgHover : Colors.transparent,
            borderRadius: BorderRadius.circular(999),
            border: Border.all(color: widget.active ? pt.accent : pt.border),
          ),
          child: Row(mainAxisSize: MainAxisSize.min, children: [
            Icon(widget.icon, size: 11, color: widget.active ? pt.accent : pt.fgMuted),
            const SizedBox(width: 4),
            Text(widget.label, style: TextStyle(
              color: widget.active ? pt.accent : pt.fgMuted,
              fontSize: 12,
              fontWeight: widget.active ? FontWeight.w600 : FontWeight.w400,
            )),
          ]),
        ),
      ),
    );
  }
}

class _Footer extends StatelessWidget {
  final int count;
  final bool loading;
  final PyramidTheme pt;
  const _Footer({required this.count, required this.loading, required this.pt});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      decoration: BoxDecoration(
        color: pt.bg2,
        border: Border(top: BorderSide(color: pt.border)),
        borderRadius: BorderRadius.only(bottomLeft: Radius.circular(pt.rXl), bottomRight: Radius.circular(pt.rXl)),
      ),
      child: Row(
        children: [
          _Hint('↑↓', 'navigieren', pt),
          const SizedBox(width: 16),
          _Hint('↵', 'öffnen', pt),
          const Spacer(),
          if (loading)
            SizedBox(width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 1.5, color: pt.fgDim))
          else
            Text(
              '$count Ergebnis${count == 1 ? '' : 'se'}',
              style: TextStyle(color: pt.fgDim, fontSize: 11),
            ),
        ],
      ),
    );
  }
}

class _Hint extends StatelessWidget {
  final String key_;
  final String label;
  final PyramidTheme pt;
  const _Hint(this.key_, this.label, this.pt);
  @override
  Widget build(BuildContext context) => Row(children: [
    Container(
      padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
      decoration: BoxDecoration(color: pt.bg3, borderRadius: BorderRadius.circular(4), border: Border.all(color: pt.border)),
      child: Text(key_, style: TextStyle(color: pt.fgMuted, fontSize: 11, fontFamily: 'monospace')),
    ),
    const SizedBox(width: 4),
    Text(label, style: TextStyle(color: pt.fgDim, fontSize: 11)),
  ]);
}
