import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';

import '../../../../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
import '../../../../extensions/date_time.dart';
import '../data/calendar_layout.dart';
import '../data/lesson_period_schedule.dart';
import 'time_region_tile.dart';

class SpecialRegionsBuilder {
  final TimetableGetHolidaysResponse holidays;
  final LessonPeriodSchedule schedule;
  final ColorScheme colorScheme;
  final Color disabledColor;

  SpecialRegionsBuilder({
    required this.holidays,
    required this.schedule,
    required this.colorScheme,
    required this.disabledColor,
  });

  List<TimeRegion> build() {
    final rangeStart = DateTime.now()
        .subtractDays(14)
        .nextWeekday(DateTime.monday);
    // Far enough out to cover comfortable scrolling without rebuilding;
    // Syncfusion only paints regions that intersect the visible window so
    // the extra entries don't hurt rendering cost.
    final rangeEnd = DateTime.now().addDays(180);

    final holidayRegions = _buildHolidayRegions().toList();
    final holidayDays = holidayRegions
        .map((r) => _dayKey(r.startTime))
        .toSet();

    // Materialise one TimeRegion per break per non-holiday day. Tried
    // `recurrenceRule: FREQ=DAILY` with `recurrenceExceptionDates` first,
    // but Syncfusion's recurrence exception matching is unreliable in
    // practice — the break overlay kept showing on holiday days. Generating
    // explicit per-day regions and just skipping holidays is robust.
    final breakPeriods = schedule.periods.where((p) => p.isBreak).toList();
    final breakRegions = <TimeRegion>[];
    for (var day = rangeStart; !day.isAfter(rangeEnd); day = day.addDays(1)) {
      if (holidayDays.contains(_dayKey(day))) continue;
      for (final p in breakPeriods) {
        final start = day.copyWith(
          hour: p.start.hour,
          minute: p.start.minute,
        );
        breakRegions.add(_breakRegion(start, p.duration));
      }
    }

    return [...holidayRegions, ...breakRegions];
  }

  static String _dayKey(DateTime d) => '${d.year}-${d.month}-${d.day}';

  Iterable<TimeRegion> _buildHolidayRegions() {
    // Multiple holiday entries can cover the same day (e.g. a public holiday
    // falling inside a vacation period). Collapse them per-day so we emit
    // exactly one TimeRegion per day and the overlapping labels don't render
    // on top of each other.
    final byDay = <String, _HolidayDay>{};
    for (final holiday in holidays.result) {
      final startDay = DateTime(
        holiday.startDate.year,
        holiday.startDate.month,
        holiday.startDate.day,
      );
      final endDay = DateTime(
        holiday.endDate.year,
        holiday.endDate.month,
        holiday.endDate.day,
      );
      // Webuntis treats endDate inclusively (last day of the break) — the
      // `+ 1` covers single-day public holidays (where startDate == endDate)
      // and the final day of a multi-day vacation, both of which would
      // otherwise be skipped.
      final dayCount = endDay.difference(startDay).inDays + 1;
      for (var i = 0; i < dayCount; i++) {
        final day = startDay.addDays(i);
        final key = _dayKey(day);
        byDay.putIfAbsent(key, () => _HolidayDay(day, [])).names.add(
          holiday.longName.isNotEmpty ? holiday.longName : holiday.shortName,
        );
      }
    }
    final gridStartHour = kCalendarStartHour.floor();
    final gridStartMinute = ((kCalendarStartHour - gridStartHour) * 60).round();
    final gridEndHour = kCalendarEndHour.floor();
    final gridEndMinute = ((kCalendarEndHour - gridEndHour) * 60).round();
    return byDay.values.map(
      (entry) => TimeRegion(
        startTime: entry.day.copyWith(
          hour: gridStartHour,
          minute: gridStartMinute,
        ),
        endTime: entry.day.copyWith(
          hour: gridEndHour,
          minute: gridEndMinute,
        ),
        text: '$kTimeRegionHolidayPrefix${entry.names.join(" / ")}',
        color: disabledColor.withAlpha(50),
      ),
    );
  }

  TimeRegion _breakRegion(DateTime start, Duration duration) => TimeRegion(
    startTime: start,
    endTime: start.add(duration),
    text: kTimeRegionCenterIcon,
    color: colorScheme.primary.withAlpha(50),
    iconData: Icons.restaurant,
  );
}

class _HolidayDay {
  final DateTime day;
  final List<String> names;
  _HolidayDay(this.day, this.names);
}
