import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../../api/errors/error_mapper.dart';
import '../../../api/marianumconnect/queries/timetable_get_classes/timetable_get_classes.dart';
import '../../../api/marianumconnect/queries/timetable_get_element_week/timetable_element_type.dart';
import '../../../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart';
import '../../../api/marianumconnect/queries/timetable_get_students/timetable_get_students.dart';
import '../../../api/marianumconnect/queries/timetable_get_teachers/timetable_get_teachers.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../storage/timetable_favorites_settings.dart';
import '../../../utils/haptics.dart';
import '../../../widget/app_progress_indicator.dart';

/// Full-screen picker: choose an element type (or "Alle" to search across all
/// types), filter the (potentially large) list client-side, and pick an element
/// whose timetable is then shown inline. Starred elements appear at the top for
/// quick access.
class ElementPickerPage extends StatefulWidget {
  const ElementPickerPage({super.key});

  @override
  State<ElementPickerPage> createState() => _ElementPickerPageState();
}

class _ElementPickerPageState extends State<ElementPickerPage> {
  /// `null` = the "Alle" tab (search across every type). Default.
  TimetableElementType? _selectedType;
  String _query = '';
  final TextEditingController _searchController = TextEditingController();

  // One in-flight/resolved future per type so switching tabs (or rebuilds)
  // never re-fetches a list that's already loaded.
  final Map<TimetableElementType, Future<List<_PickerItem>>> _futures = {};
  // Memoised combined future for the "Alle" tab; rebuilt on retry.
  Future<List<_PickerItem>>? _allFuture;

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

  Future<List<_PickerItem>> _currentFuture() {
    final type = _selectedType;
    if (type != null) return _loadFor(type);
    return _allFuture ??= _loadAll();
  }

  Future<List<_PickerItem>> _loadFor(TimetableElementType type) =>
      _futures.putIfAbsent(type, () => _fetch(type));

  Future<List<_PickerItem>> _loadAll() async {
    final lists = await Future.wait(TimetableElementType.values.map(_loadFor));
    return lists.expand((e) => e).toList();
  }

  Future<List<_PickerItem>> _fetch(TimetableElementType type) async {
    switch (type) {
      case TimetableElementType.student:
        final r = await TimetableGetStudents().run();
        return r.result
            .map(
              (s) => _PickerItem(
                type: type,
                id: s.id,
                primary: s.displayName,
                secondary: '${s.lastName}, ${s.firstName}',
                search: '${s.displayName} ${s.firstName} ${s.lastName}'
                    .toLowerCase(),
              ),
            )
            .toList();
      case TimetableElementType.teacher:
        final r = await TimetableGetTeachers().run();
        return r.result
            .map(
              (t) => _PickerItem(
                type: type,
                id: t.id,
                primary: t.displayName,
                secondary: t.shortName,
                search: '${t.displayName} ${t.shortName}'.toLowerCase(),
              ),
            )
            .toList();
      case TimetableElementType.room:
        final r = await TimetableGetRooms().run();
        return r.result
            .map(
              (rm) => _PickerItem(
                type: type,
                id: rm.id,
                primary: rm.shortName,
                secondary: rm.longName,
                search: '${rm.shortName} ${rm.longName}'.toLowerCase(),
              ),
            )
            .toList();
      case TimetableElementType.schoolClass:
        final r = await TimetableGetClasses().run();
        return r.result
            .map(
              (c) => _PickerItem(
                type: type,
                id: c.id,
                primary: c.shortName,
                secondary: c.longName,
                search: '${c.shortName} ${c.longName}'.toLowerCase(),
              ),
            )
            .toList();
    }
  }

  void _open(_PickerItem item) {
    Haptics.selection();
    // Hand the selection back to the timetable view, which renders the foreign
    // plan inline. We do not navigate to a new page.
    Navigator.of(context).pop((
      type: item.type,
      id: item.id,
      label: item.primary,
    ));
  }

  void _openFavorite(FavoriteTimetableElement favorite) {
    Haptics.selection();
    Navigator.of(context).pop((
      type: favorite.type,
      id: favorite.id,
      label: favorite.label,
    ));
  }

  void _toggleFavorite(TimetableElementType type, int id, String label) {
    Haptics.selection();
    context
        .read<SettingsCubit>()
        .val(write: true)
        .timetableFavoritesSettings
        .toggle(type, id, label);
  }

  @override
  Widget build(BuildContext context) {
    final favorites = context
        .watch<SettingsCubit>()
        .val()
        .timetableFavoritesSettings
        .favorites;

    return Scaffold(
      appBar: AppBar(title: const Text('Stundenplan öffnen')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
            child: TextField(
              controller: _searchController,
              onChanged: (value) => setState(() => _query = value.trim()),
              decoration: InputDecoration(
                hintText: 'Suchen…',
                prefixIcon: const Icon(Icons.search),
                suffixIcon: _query.isEmpty
                    ? null
                    : IconButton(
                        icon: const Icon(Icons.clear),
                        tooltip: 'Leeren',
                        onPressed: () {
                          _searchController.clear();
                          setState(() => _query = '');
                        },
                      ),
                border: const OutlineInputBorder(),
                isDense: true,
              ),
            ),
          ),
          SizedBox(height: 40, child: _typeSelector()),
          const SizedBox(height: 8),
          Expanded(child: _list(favorites)),
        ],
      ),
    );
  }

  Widget _typeSelector() {
    return ListView(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      children: [
        _typeChip(null),
        ...TimetableElementType.values.map(_typeChip),
      ],
    );
  }

  Widget _typeChip(TimetableElementType? type) {
    return Padding(
      padding: const EdgeInsets.only(right: 8),
      child: ChoiceChip(
        avatar: Icon(type == null ? Icons.apps : _iconFor(type), size: 18),
        showCheckmark: false,
        label: Text(type?.label ?? 'Alle'),
        selected: _selectedType == type,
        onSelected: (_) {
          Haptics.selection();
          setState(() => _selectedType = type);
        },
      ),
    );
  }

  Widget _list(List<FavoriteTimetableElement> favorites) {
    return FutureBuilder<List<_PickerItem>>(
      future: _currentFuture(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: AppProgressIndicator.medium());
        }
        if (snapshot.hasError) {
          return _ErrorBody(
            message: errorToUserMessage(snapshot.error),
            onRetry: () => setState(() {
              _futures.clear();
              _allFuture = null;
            }),
          );
        }

        final items = snapshot.data ?? const <_PickerItem>[];
        final query = _query.toLowerCase();
        final filtered = query.isEmpty
            ? items
            : items.where((i) => i.search.contains(query)).toList();

        // On a specific type tab only that type's favorites are relevant; the
        // "Alle" tab shows them all.
        final visibleFavorites = _selectedType == null
            ? favorites
            : favorites.where((f) => f.type == _selectedType).toList();

        // Favorites only make sense while not actively searching.
        final showFavorites = _query.isEmpty && visibleFavorites.isNotEmpty;

        if (filtered.isEmpty && !showFavorites) {
          return const Center(child: Text('Keine Einträge gefunden.'));
        }

        // Leading rows when favorites are shown: a "Favoriten" header, one row
        // per favorite, then a divider + "Weitere" header that separates them
        // from the regular results.
        final headerCount = showFavorites ? visibleFavorites.length + 2 : 0;

        return ListView.builder(
          itemCount: filtered.length + headerCount,
          itemBuilder: (context, index) {
            if (showFavorites) {
              if (index == 0) return _sectionHeader('Favoriten');
              if (index <= visibleFavorites.length) {
                return _favoriteTile(visibleFavorites[index - 1]);
              }
              if (index == visibleFavorites.length + 1) {
                return Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Divider(height: 1),
                    _sectionHeader('Weitere'),
                  ],
                );
              }
            }
            final item = filtered[index - headerCount];
            return _itemTile(item);
          },
        );
      },
    );
  }

  Widget _sectionHeader(String label) {
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 10, 16, 4),
      child: Text(
        label,
        style: theme.textTheme.labelMedium?.copyWith(
          color: theme.colorScheme.primary,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

  Widget _favoriteTile(FavoriteTimetableElement favorite) {
    return ListTile(
      leading: Icon(_iconFor(favorite.type)),
      title: Text(favorite.label),
      subtitle: Text(favorite.type.label),
      trailing: IconButton(
        icon: const Icon(Icons.star),
        tooltip: 'Favorit entfernen',
        onPressed: () =>
            _toggleFavorite(favorite.type, favorite.id, favorite.label),
      ),
      onTap: () => _openFavorite(favorite),
    );
  }

  Widget _itemTile(_PickerItem item) {
    final isFavorite = context
        .watch<SettingsCubit>()
        .val()
        .timetableFavoritesSettings
        .isFavorite(item.type, item.id);

    // In the "Alle" tab the type is otherwise ambiguous, so surface it.
    final subtitleParts = <String>[
      if (item.secondary.isNotEmpty && item.secondary != item.primary)
        item.secondary,
      if (_selectedType == null) item.type.label,
    ];

    return ListTile(
      leading: Icon(_iconFor(item.type)),
      title: Text(item.primary),
      subtitle: subtitleParts.isEmpty
          ? null
          : Text(subtitleParts.join(' · ')),
      trailing: IconButton(
        icon: Icon(isFavorite ? Icons.star : Icons.star_border),
        tooltip: isFavorite ? 'Favorit entfernen' : 'Als Favorit markieren',
        onPressed: () => _toggleFavorite(item.type, item.id, item.primary),
      ),
      onTap: () => _open(item),
    );
  }

  static IconData _iconFor(TimetableElementType type) {
    switch (type) {
      case TimetableElementType.student:
        return Icons.person_outline;
      case TimetableElementType.teacher:
        return Icons.school_outlined;
      case TimetableElementType.room:
        return Icons.meeting_room_outlined;
      case TimetableElementType.schoolClass:
        return Icons.groups_outlined;
    }
  }
}

class _PickerItem {
  final TimetableElementType type;
  final int id;
  final String primary;
  final String secondary;
  final String search;

  _PickerItem({
    required this.type,
    required this.id,
    required this.primary,
    required this.secondary,
    required this.search,
  });
}

class _ErrorBody extends StatelessWidget {
  final String message;
  final VoidCallback onRetry;

  const _ErrorBody({required this.message, required this.onRetry});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Icon(Icons.error_outline, size: 40),
            const SizedBox(height: 12),
            Text(message, textAlign: TextAlign.center),
            const SizedBox(height: 16),
            FilledButton.tonal(
              onPressed: onRetry,
              child: const Text('Erneut versuchen'),
            ),
          ],
        ),
      ),
    );
  }
}
