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

import '../../../api/marianumconnect/queries/timetable_get_element_week/timetable_element_type.dart';
import '../../../extensions/date_time.dart';
import '../../../routing/app_routes.dart';
import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
import '../../../state/app/modules/capabilities/bloc/capabilities_cubit.dart';
import '../../../state/app/modules/foreign_timetable/bloc/foreign_timetable_bloc.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
import '../../../state/app/modules/timetable/bloc/timetable_state.dart';
import '../../../utils/haptics.dart';
import '../../../widget/demo_restricted.dart';
import 'custom_events/custom_event_edit_dialog.dart';
import 'details/appointment_details_dispatcher.dart';
import 'widgets/timetable_calendar_view.dart';

enum _CalendarAction { addEvent, viewEvents }

class Timetable extends StatefulWidget {
  const Timetable({super.key});

  @override
  State<Timetable> createState() => _TimetableState();
}

class _TimetableState extends State<Timetable> {
  final GlobalKey<TimetableCalendarViewState> _calendarKey =
      GlobalKey<TimetableCalendarViewState>();

  /// When non-null the view shows this element's plan inline instead of the
  /// user's own. Cleared (back to own plan) via the viewing banner.
  TimetableElementRef? _selected;

  DateTime _initialDisplayDate() => DateTime.now().addDays(2);

  void _jumpToToday() {
    _calendarKey.currentState?.jumpToToday();
  }

  bool _isOnInitialWeek(TimetableState state) =>
      state.startDate == _mondayOf(_initialDisplayDate());

  Future<void> _openPicker() async {
    if (guardDemoAction(context)) return;
    final ref = await AppRoutes.openElementPicker(context);
    if (!mounted || ref == null) return;
    setState(() => _selected = ref);
  }

  void _backToOwnPlan() {
    setState(() => _selected = null);
  }

  void _onAction(_CalendarAction action) {
    switch (action) {
      case _CalendarAction.addEvent:
        showDialog(
          context: context,
          builder: (_) => const CustomEventEditDialog(),
          barrierDismissible: false,
        );
      case _CalendarAction.viewEvents:
        AppRoutes.openCustomEvents(context);
    }
  }

  void _onCreateEventAt(DateTime start, DateTime end) {
    showDialog(
      context: context,
      builder: (_) =>
          CustomEventEditDialog(initialStart: start, initialEnd: end),
      barrierDismissible: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    final selected = _selected;
    if (selected == null) return _buildOwnPlan(context);
    // Scope the foreign bloc to the current selection so switching elements
    // (or back to the own plan) tears it down and builds a fresh one.
    return BlocProvider<ForeignTimetableBloc>(
      key: ValueKey('${selected.type.name}-${selected.id}'),
      create: (_) => ForeignTimetableBloc(
        type: selected.type,
        elementId: selected.id,
        title: selected.label,
      ),
      // Builder gives us a context *below* the provider so the foreign bloc is
      // resolvable inside _buildForeignPlan.
      child: Builder(
        builder: (context) => _buildForeignPlan(context, selected),
      ),
    );
  }

  Widget _buildOwnPlan(BuildContext context) {
    final bloc = context.read<TimetableBloc>();
    final loadableState = context.watch<TimetableBloc>().state;
    final innerState = loadableState.data;
    final atToday = innerState != null && _isOnInitialWeek(innerState);
    final capabilities = context.watch<CapabilitiesCubit>();
    final canViewForeign = capabilities.canViewForeignTimetables;
    return Scaffold(
      appBar: AppBar(
        // Der Kalender scrollt nicht (nur ziehen/reloaden), aber seine internen
        // Scrollables feuern ScrollNotifications, die sonst den Material-3
        // "scrolled under"-Farbwechsel der AppBar dauerhaft auslösen.
        notificationPredicate: (_) => false,
        title: const Text('Stunden & Vertretungsplan'),
        actions: [
          IconButton(
            icon: const Icon(Icons.home_outlined),
            tooltip: 'Zur aktuellen Woche',
            onPressed: atToday ? null : _jumpToToday,
          ),
          PopupMenuButton<_CalendarAction>(
            tooltip: 'Kalendereinträge',
            icon: const Icon(Icons.edit_calendar_outlined),
            onSelected: _onAction,
            itemBuilder: (_) => const [
              PopupMenuItem(
                value: _CalendarAction.addEvent,
                child: ListTile(
                  title: Text('Kalendereintrag hinzufügen'),
                  leading: Icon(Icons.add),
                ),
              ),
              PopupMenuItem(
                value: _CalendarAction.viewEvents,
                child: ListTile(
                  title: Text('Kalendereinträge anzeigen'),
                  leading: Icon(Icons.perm_contact_calendar_outlined),
                ),
              ),
            ],
          ),
          if (canViewForeign)
            IconButton(
              icon: const Icon(Icons.person_search),
              tooltip: 'Anderen Stundenplan öffnen',
              onPressed: _openPicker,
            ),
        ],
      ),
      body: LoadableStateConsumer<TimetableBloc, TimetableState>(
        // Without this predicate the consumer treats the freshly-initialised
        // empty TimetableState as "has content" and only shows the error bar
        // on top — but the calendar view collapses to `SizedBox.shrink()`
        // while the reference data is missing, leaving the user with a blank
        // screen. Telling the consumer that "ready" means having reference
        // data flips it into the proper error-screen path instead.
        isReady: (state) => state.hasReferenceData,
        child: (state, _) => TimetableCalendarView(
          key: _calendarKey,
          state: state,
          onWeekChanged: bloc.changeWeek,
          onAppointmentTap: (apt) => AppointmentDetailsDispatcher.show(
            context,
            state,
            apt,
            canEditSubjectColor: true,
          ),
          onCreateEvent: _onCreateEventAt,
          customEvents: state.customEvents?.events ?? const [],
          showClassInsteadOfTeacher: capabilities.isTeacher,
        ),
      ),
    );
  }

  Widget _buildForeignPlan(BuildContext context, TimetableElementRef selected) {
    final bloc = context.read<ForeignTimetableBloc>();
    final loadableState = context.watch<ForeignTimetableBloc>().state;
    final innerState = loadableState.data;
    final atToday = innerState != null && _isOnInitialWeek(innerState);
    final canViewForeign = context
        .watch<CapabilitiesCubit>()
        .canViewForeignTimetables;
    return Scaffold(
      appBar: AppBar(
        // Siehe _buildOwnPlan: den scroll-under-Farbwechsel unterdrücken, weil
        // der Kalender nicht scrollt, aber ScrollNotifications feuert.
        notificationPredicate: (_) => false,
        title: const Text('Stunden & Vertretungsplan'),
        actions: [
          IconButton(
            icon: const Icon(Icons.home_outlined),
            tooltip: 'Zur aktuellen Woche',
            onPressed: atToday ? null : _jumpToToday,
          ),
          if (canViewForeign)
            IconButton(
              icon: const Icon(Icons.person_search),
              tooltip: 'Anderen Stundenplan öffnen',
              onPressed: _openPicker,
            ),
        ],
      ),
      body: Column(
        children: [
          _ViewingBanner(element: selected, onClose: _backToOwnPlan),
          Expanded(
            child: LoadableStateConsumer<ForeignTimetableBloc, TimetableState>(
              // Foreign plans never carry custom events, so unlike the own-plan
              // view we must not require `customEvents` here.
              isReady: (state) =>
                  state.rooms != null &&
                  state.subjects != null &&
                  state.schoolHolidays != null,
              child: (state, _) => TimetableCalendarView(
                key: _calendarKey,
                state: state,
                onWeekChanged: bloc.changeWeek,
                onAppointmentTap: (apt) =>
                    AppointmentDetailsDispatcher.show(context, state, apt),
                customEvents: const [],
                showClassInsteadOfTeacher:
                    selected.type == TimetableElementType.teacher,
              ),
            ),
          ),
        ],
      ),
    );
  }

  static DateTime _mondayOf(DateTime d) {
    final monday = d.subtractDays(d.weekday - 1);
    return DateTime(monday.year, monday.month, monday.day);
  }
}

/// Slim banner shown at the top of the timetable while a foreign element's plan
/// is being viewed. Displays which element is shown, lets the user star it, and
/// offers a one-tap return to the own plan.
class _ViewingBanner extends StatelessWidget {
  final TimetableElementRef element;
  final VoidCallback onClose;

  const _ViewingBanner({required this.element, required this.onClose});

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

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final isFavorite = context
        .watch<SettingsCubit>()
        .val()
        .timetableFavoritesSettings
        .isFavorite(element.type, element.id);

    final onColor = theme.colorScheme.onSecondaryContainer;
    // Compact icon button: ~32px square, no extra padding, so the banner stays
    // slim instead of inheriting the default 48px touch target height.
    Widget compactButton({
      required IconData icon,
      required String tooltip,
      required VoidCallback onPressed,
    }) => IconButton(
      icon: Icon(icon),
      iconSize: 18,
      color: onColor,
      tooltip: tooltip,
      visualDensity: VisualDensity.compact,
      padding: EdgeInsets.zero,
      constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
      onPressed: onPressed,
    );

    return Material(
      color: theme.colorScheme.secondaryContainer,
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16, 2, 6, 2),
        child: Row(
          children: [
            Icon(_iconFor(element.type), size: 16, color: onColor),
            const SizedBox(width: 10),
            Expanded(
              child: Text(
                element.label,
                style: theme.textTheme.labelLarge?.copyWith(color: onColor),
                overflow: TextOverflow.ellipsis,
              ),
            ),
            compactButton(
              icon: isFavorite ? Icons.star : Icons.star_border,
              tooltip: isFavorite
                  ? 'Favorit entfernen'
                  : 'Als Favorit markieren',
              onPressed: () => _toggleFavorite(context),
            ),
            const SizedBox(width: 4),
            compactButton(
              icon: Icons.close,
              tooltip: 'Zurück zum eigenen Plan',
              onPressed: onClose,
            ),
          ],
        ),
      ),
    );
  }

  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;
    }
  }
}
