import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';

import '../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
import '../../../routing/app_routes.dart';
import '../../../storage/modules_settings.dart';
import '../../../view/pages/absence_report/absence_report_view.dart';
import '../../../view/pages/files/files.dart';
import '../../../view/pages/grade_averages/grade_averages_view.dart';
import '../../../view/pages/holidays/holidays_view.dart';
import '../../../view/pages/marianum_dates/marianum_dates_view.dart';
import '../../../view/pages/marianum_message/marianum_message_list_view.dart';
import '../../../view/pages/more/roomplan/roomplan.dart';
import '../../../view/pages/talk/chat_list.dart';
import '../../../view/pages/ticker/ticker_view.dart';
import '../../../view/pages/timetable/timetable.dart';
import '../../../widget/breaker/breaker.dart';
import '../../../widget/centered_leading.dart';
import '../infrastructure/loadable_state/loadable_state.dart';
import 'chat_list/bloc/chat_list_bloc.dart';
import 'chat_list/bloc/chat_list_state.dart';
import 'settings/bloc/settings_cubit.dart';

class AppModule {
  Modules module;
  String name;
  Widget Function() icon;
  BreakerArea breakerArea;
  Widget Function() create;

  AppModule(
    this.module, {
    required this.name,
    required this.icon,
    this.breakerArea = BreakerArea.global,
    required this.create,
  });

  static Map<Modules, AppModule> modules(
    BuildContext context, {
    bool showFiltered = false,
  }) {
    final settings = context.read<SettingsCubit>();
    var available = {
      Modules.timetable: AppModule(
        Modules.timetable,
        name: 'Stundenplan',
        icon: () => Icon(Icons.calendar_month),
        breakerArea: BreakerArea.timetable,
        create: Timetable.new,
      ),
      Modules.ticker: AppModule(
        Modules.ticker,
        name: 'Ticker',
        // Icons.newspaper is already taken by the "Marianum Message" module;
        // use campaign to keep the two visually distinct.
        icon: () => Icon(Icons.campaign),
        breakerArea: BreakerArea.ticker,
        create: TickerView.new,
      ),
      Modules.talk: AppModule(
        Modules.talk,
        name: 'Talk',
        icon: () => BlocBuilder<ChatListBloc, LoadableState<ChatListState>>(
          builder: (context, state) {
            final rooms = state.data?.rooms;
            if (rooms == null || rooms.data.isEmpty) {
              return const Icon(Icons.chat);
            }
            final messages = rooms.data
                .map((e) => e.unreadMessages)
                .reduce((a, b) => a + b);
            return badges.Badge(
              showBadge: messages > 0,
              position: badges.BadgePosition.topEnd(top: -3, end: -3),
              stackFit: StackFit.loose,
              badgeStyle: badges.BadgeStyle(
                padding: const EdgeInsets.all(3),
                badgeColor: Theme.of(context).primaryColor,
                elevation: 1,
              ),
              badgeContent: Text(
                '$messages',
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                ),
              ),
              child: const Icon(Icons.chat),
            );
          },
        ),
        breakerArea: BreakerArea.talk,
        create: ChatList.new,
      ),
      Modules.files: AppModule(
        Modules.files,
        name: 'Dateien',
        icon: () => Icon(Icons.folder),
        breakerArea: BreakerArea.files,
        create: Files.new,
      ),
      Modules.marianumMessage: AppModule(
        Modules.marianumMessage,
        name: 'Marianum Message',
        icon: () => Icon(Icons.newspaper),
        breakerArea: BreakerArea.news,
        create: MarianumMessageListView.new,
      ),
      Modules.roomPlan: AppModule(
        Modules.roomPlan,
        name: 'Raumplan',
        icon: () => Icon(Icons.location_pin),
        breakerArea: BreakerArea.roomPlan,
        create: Roomplan.new,
      ),
      Modules.gradeAveragesCalculator: AppModule(
        Modules.gradeAveragesCalculator,
        name: 'Notendurchschnittsrechner',
        icon: () => Icon(Icons.calculate),
        breakerArea: BreakerArea.grades,
        create: GradeAveragesView.new,
      ),
      Modules.holidays: AppModule(
        Modules.holidays,
        name: 'Schulferien',
        icon: () => Icon(Icons.beach_access_outlined),
        breakerArea: BreakerArea.holidays,
        create: HolidaysView.new,
      ),
      Modules.marianumDates: AppModule(
        Modules.marianumDates,
        name: 'Marianum Termine',
        icon: () => Icon(Icons.event_note),
        breakerArea: BreakerArea.dates,
        create: MarianumDatesView.new,
      ),
      Modules.absenceReport: AppModule(
        Modules.absenceReport,
        name: 'Abwesenheitsmeldung',
        icon: () => Icon(Icons.sick_outlined),
        create: AbsenceReportView.new,
      ),
    };

    if (!showFiltered) {
      available.removeWhere(
        (key, value) =>
            settings.val().modulesSettings.hiddenModules.contains(key),
      );
    }

    return {
      for (var element in effectiveModuleOrder(
        settings.val().modulesSettings,
      ).where((element) => available.containsKey(element)))
        element: available[element]!,
    };
  }

  // A persisted moduleOrder predates modules added in newer app versions.
  // Deriving the effective order at read time (instead of relying on a heal
  // during hydration) makes it impossible for a module to vanish from the
  // bottom bar, the "Mehr" menu and the settings list at once. Missing
  // modules are inserted right after their closest declared predecessor so
  // they land at their default position (e.g. the ticker between Stundenplan
  // and Talk) instead of at the end of the "Mehr" menu.
  static List<Modules> effectiveModuleOrder(ModulesSettings settings) {
    final seen = <Modules>{};
    final order = [
      for (final m in settings.moduleOrder)
        if (seen.add(m)) m,
    ];
    for (final missing in Modules.values) {
      if (!seen.add(missing)) continue;
      var insertAt = 0;
      for (final predecessor in Modules.values.takeWhile(
        (m) => m != missing,
      )) {
        final pos = order.indexOf(predecessor);
        if (pos >= insertAt) insertAt = pos + 1;
      }
      order.insert(insertAt, missing);
    }
    return order;
  }

  // The settings list displays a capability-filtered subset, so reorder
  // indices refer to that subset; the move is applied there and merged back
  // into the full order (non-displayed modules keep their relative slots).
  static List<Modules> reorderModuleOrder({
    required List<Modules> displayed,
    required List<Modules> effective,
    required int oldIndex,
    required int newIndex,
  }) {
    final moved = List<Modules>.from(displayed);
    moved.insert(newIndex, moved.removeAt(oldIndex));
    final displayedSet = displayed.toSet();
    var moveIndex = 0;
    return [
      for (final m in effective)
        displayedSet.contains(m) ? moved[moveIndex++] : m,
    ];
  }

  static const int minBottomBarSlots = 3;
  static const int maxBottomBarSlots = 5;

  static int resolveBottomBarSlotCount(BuildContext context) {
    final settings = context.read<SettingsCubit>().val().modulesSettings;
    final available = modules(context).length;

    int desired;
    if (settings.autoFillBottomBar) {
      // 4 content tabs so the bar shows 5 entries including "Mehr" — enough
      // for the Ticker module without pushing "Dateien" into the overflow.
      desired = 4;
    } else {
      desired = settings.fixedBottomBarSlots;
    }

    desired = desired.clamp(minBottomBarSlots, maxBottomBarSlots);
    return desired.clamp(0, available);
  }

  static List<AppModule> getBottomBarModules(BuildContext context) {
    final all = modules(context).values.toList();
    final slots = resolveBottomBarSlotCount(context);
    return all.take(slots).toList();
  }

  static List<AppModule> getOverhangModules(BuildContext context) {
    final all = modules(context).values.toList();
    final slots = resolveBottomBarSlotCount(context);
    return all.skip(slots).toList();
  }

  Widget toListTile(
    BuildContext context, {
    Key? key,
    bool isReorder = false,
    Function()? onVisibleChange,
    bool isVisible = true,
  }) => ListTile(
    key: key,
    leading: CenteredLeading(icon()),
    title: Text(name),
    onTap: isReorder ? null : () => AppRoutes.openModule(context, this),
    trailing: isReorder
        ? Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconButton(
                tooltip: isVisible ? 'Modul ausblenden' : 'Modul einblenden',
                onPressed: onVisibleChange,
                icon: Icon(
                  isVisible
                      ? Icons.visibility_outlined
                      : Icons.visibility_off_outlined,
                ),
              ),
              Icon(Icons.drag_handle_outlined),
            ],
          )
        : const Icon(Icons.arrow_right),
  );

  PersistentTabConfig toBottomTab(
    BuildContext context, {
    Widget Function(IconData icon)? iconBuilder,
  }) => PersistentTabConfig(
    screen: Breaker(breaker: breakerArea, child: create()),
    item: ItemConfig(
      activeForegroundColor: Theme.of(context).primaryColor,
      inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
      icon: icon(),
      title: name,
    ),
  );
}

enum Modules {
  timetable,
  ticker,
  talk,
  files,
  marianumMessage,
  roomPlan,
  gradeAveragesCalculator,
  holidays,
  marianumDates,
  absenceReport,
}
