part of '../custom_workweek_calendar.dart';

class _WeekGrid extends StatelessWidget {
  final DateTime weekStart;
  final LessonPeriodSchedule schedule;
  final List<Appointment> appointments;
  final List<TimeRegion> timeRegions;
  final void Function(Appointment) onAppointmentTap;
  final bool Function(Appointment) isCrossedOut;
  final void Function(DateTime start, DateTime end)? onCreateEvent;
  final DateTime today;
  final ValueListenable<DateTime> nowNotifier;
  final double rulerWidth;
  final PeriodLayout layout;

  const _WeekGrid({
    required this.weekStart,
    required this.schedule,
    required this.appointments,
    required this.timeRegions,
    required this.onAppointmentTap,
    required this.isCrossedOut,
    required this.onCreateEvent,
    required this.today,
    required this.nowNotifier,
    required this.rulerWidth,
    required this.layout,
  });

  @override
  Widget build(BuildContext context) {
    final partitioned = partitionAppointmentsForWeek(appointments, weekStart);

    return Row(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        // Reine Orientierungshilfe – der Screenreader soll die Zeitleiste nicht
        // vorlesen; jede Termin-Kachel trägt ihre Uhrzeit selbst im Label.
        ExcludeSemantics(
          child: _PeriodRuler(
            schedule: schedule,
            layout: layout,
            width: rulerWidth,
          ),
        ),
        for (var d = 0; d < 5; d++)
          Expanded(
            child: _DayColumn(
              date: weekStart.addDays(d),
              schedule: schedule,
              appointments: partitioned.inside[d],
              timeRegions: timeRegions,
              layout: layout,
              today: today,
              nowNotifier: nowNotifier,
              onAppointmentTap: onAppointmentTap,
              isCrossedOut: isCrossedOut,
              onCreateEvent: onCreateEvent,
            ),
          ),
      ],
    );
  }
}

class _PeriodRuler extends StatelessWidget {
  final LessonPeriodSchedule schedule;
  final PeriodLayout layout;
  final double width;

  const _PeriodRuler({
    required this.schedule,
    required this.layout,
    required this.width,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return SizedBox(
      width: width,
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          for (final period in schedule.periods)
            Positioned(
              top: layout.topOf(period),
              height: layout.heightOf(period),
              left: 0,
              right: 0,
              child: _PeriodLabel(period: period, theme: theme),
            ),
        ],
      ),
    );
  }
}

class _PeriodLabel extends StatelessWidget {
  final LessonPeriod period;
  final ThemeData theme;

  const _PeriodLabel({required this.period, required this.theme});

  @override
  Widget build(BuildContext context) {
    final dividerColor = theme.dividerColor.withAlpha(110);
    final secondaryTextColor = theme.colorScheme.onSurfaceVariant;

    if (period.isBreak) {
      return Container(
        decoration: BoxDecoration(
          border: Border(
            top: BorderSide(color: dividerColor, width: 0.5),
            bottom: BorderSide(color: dividerColor, width: 0.5),
          ),
        ),
        alignment: Alignment.center,
        child: Icon(
          Icons.coffee_outlined,
          size: 12,
          color: secondaryTextColor.withAlpha(180),
          semanticLabel: A11yLabels.breakTime,
        ),
      );
    }

    final timeStyle = theme.textTheme.labelSmall?.copyWith(
      color: secondaryTextColor.withAlpha(140),
      height: 1.0,
      fontSize: 9,
    );
    const tightTextHeight = TextHeightBehavior(
      applyHeightToFirstAscent: false,
      applyHeightToLastDescent: false,
    );

    return LayoutBuilder(
      builder: (context, constraints) {
        final showTimes = constraints.maxHeight >= 38;
        return DecoratedBox(
          decoration: BoxDecoration(
            border: Border(top: BorderSide(color: dividerColor, width: 0.5)),
          ),
          child: Stack(
            clipBehavior: Clip.none,
            alignment: Alignment.center,
            children: [
              if (showTimes)
                Positioned(
                  top: 3,
                  left: 0,
                  right: 0,
                  child: Text(
                    _format(period.start),
                    style: timeStyle,
                    textAlign: TextAlign.center,
                    textHeightBehavior: tightTextHeight,
                  ),
                ),
              Text(
                period.name,
                style: theme.textTheme.labelLarge?.copyWith(
                  color: theme.colorScheme.onSurface,
                  fontWeight: FontWeight.w500,
                  height: 1.0,
                ),
                textAlign: TextAlign.center,
                textHeightBehavior: tightTextHeight,
              ),
              if (showTimes)
                Positioned(
                  bottom: 3,
                  left: 0,
                  right: 0,
                  child: Text(
                    _format(period.end),
                    style: timeStyle,
                    textAlign: TextAlign.center,
                    textHeightBehavior: tightTextHeight,
                  ),
                ),
            ],
          ),
        );
      },
    );
  }

  static String _format(TimeOfDay t) =>
      '${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
}

class _DayColumn extends StatelessWidget {
  final DateTime date;
  final LessonPeriodSchedule schedule;
  final List<Appointment> appointments;
  final List<TimeRegion> timeRegions;
  final PeriodLayout layout;
  final DateTime today;
  final ValueListenable<DateTime> nowNotifier;
  final void Function(Appointment) onAppointmentTap;
  final bool Function(Appointment) isCrossedOut;
  final void Function(DateTime start, DateTime end)? onCreateEvent;

  const _DayColumn({
    required this.date,
    required this.schedule,
    required this.appointments,
    required this.timeRegions,
    required this.layout,
    required this.today,
    required this.nowNotifier,
    required this.onAppointmentTap,
    required this.isCrossedOut,
    required this.onCreateEvent,
  });

  bool _overlapsExistingAppointment(
    DateTime start,
    DateTime end,
    List<Appointment> dayAppts,
  ) {
    for (final a in dayAppts) {
      if (a.endTime.isAfter(start) && a.startTime.isBefore(end)) return true;
    }
    return false;
  }

  void _handleLongPress(
    LongPressStartDetails details,
    List<Appointment> dayAppts,
  ) {
    if (onCreateEvent == null) return;
    final period = layout.periodAtY(details.localPosition.dy);
    if (period == null) return;

    final start = DateTime(
      date.year,
      date.month,
      date.day,
      period.start.hour,
      period.start.minute,
    );
    final end = DateTime(
      date.year,
      date.month,
      date.day,
      period.end.hour,
      period.end.minute,
    );
    if (_overlapsExistingAppointment(start, end, dayAppts)) return;

    Haptics.longPress();
    onCreateEvent!(start, end);
  }

  void _showOverflowSheet(
    BuildContext context,
    List<Appointment> appointments,
  ) {
    final sorted = [...appointments]
      ..sort((a, b) => a.startTime.compareTo(b.startTime));
    _showAppointmentOverflowSheet(
      context,
      sorted,
      onAppointmentTap: onAppointmentTap,
      isCrossedOut: isCrossedOut,
      subtitle: _overflowSubtitle,
    );
  }

  static String _overflowSubtitle(Appointment apt) {
    final time = '${apt.startTime.formatHm()}–${apt.endTime.formatHm()}';
    final loc = apt.location?.replaceAll('\n', ' · ');
    return loc != null && loc.isNotEmpty ? '$time · $loc' : time;
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    final dayAppointments = appointments;
    final dayRegions = expandRegionsForDay(timeRegions, date);
    final isToday = date.isSameDay(today);

    final isTablet = MediaQuery.of(context).size.shortestSide >= 600;
    final laidOut = assignLanes(dayAppointments, maxLanes: isTablet ? 3 : 2);

    final dayName = DateFormat(
      'EEEE',
      Localizations.localeOf(context).toString(),
    ).format(date);
    final headerLabel = isToday
        ? '$dayName, ${date.formatDateShort()}, ${A11yLabels.today}'
        : '$dayName, ${date.formatDateShort()}';

    // container + OrdinalSortKey ⇒ der Screenreader liest Tag für Tag (Mo→Fr),
    // jeden Tag vollständig, statt zeilenweise quer über alle Spalten. Der
    // Header steckt als erstes Element in der Spalte, damit „Montag: …" vor den
    // Stunden angesagt wird.
    return Semantics(
      container: true,
      sortKey: OrdinalSortKey(date.weekday.toDouble()),
      child: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onLongPressStart: (details) =>
            _handleLongPress(details, dayAppointments),
        child: DecoratedBox(
          decoration: BoxDecoration(
            color: isToday ? theme.colorScheme.primary.withAlpha(14) : null,
            border: Border(
              left: BorderSide(
                color: theme.dividerColor.withAlpha(90),
                width: 0.5,
              ),
            ),
          ),
          child: LayoutBuilder(
            builder: (context, constraints) {
              final width = constraints.maxWidth;
              return Stack(
                clipBehavior: Clip.none,
                children: [
                  Positioned(
                    top: 0,
                    left: 0,
                    right: 0,
                    child: Semantics(
                      header: true,
                      label: headerLabel,
                      child: const SizedBox(height: 1),
                    ),
                  ),
                  for (final period in schedule.periods)
                    Positioned(
                      top: layout.topOf(period),
                      left: 0,
                      right: 0,
                      child: Container(
                        height: 0.5,
                        color: theme.dividerColor.withAlpha(60),
                      ),
                    ),
                  for (final region in dayRegions)
                    Positioned(
                      top: layout.yOfDateTime(region.start),
                      height:
                          (layout.yOfDateTime(region.end) -
                                  layout.yOfDateTime(region.start))
                              .clamp(0, double.infinity),
                      left: 0,
                      right: 0,
                      child: TimeRegionTile(region: region.region),
                    ),
                  for (final cell in laidOut)
                    Positioned(
                      top: layout.yOfDateTime(cell.startTime),
                      height:
                          (layout.yOfDateTime(cell.endTime) -
                                  layout.yOfDateTime(cell.startTime))
                              .clamp(0, double.infinity),
                      left: cell.lane * width / cell.laneCount,
                      width: width / cell.laneCount,
                      child: switch (cell) {
                        LaidOutAppointment(:final appointment) => Semantics(
                          button: true,
                          label: A11yLabels.appointmentLabel(
                            subject: appointment.subject,
                            location: appointment.location ?? '',
                            start: appointment.startTime,
                            end: appointment.endTime,
                            crossedOut: isCrossedOut(appointment),
                          ),
                          onTap: () => onAppointmentTap(appointment),
                          child: ExcludeSemantics(
                            child: GestureDetector(
                              behavior: HitTestBehavior.opaque,
                              onTap: () => onAppointmentTap(appointment),
                              child: AppointmentTile(
                                appointment: appointment,
                                crossedOut: isCrossedOut(appointment),
                              ),
                            ),
                          ),
                        ),
                        LaidOutOverflow(:final appointments) => Semantics(
                          button: true,
                          label: A11yLabels.moreAppointments(
                            appointments.length,
                          ),
                          onTap: () =>
                              _showOverflowSheet(context, appointments),
                          child: ExcludeSemantics(
                            child: GestureDetector(
                              behavior: HitTestBehavior.opaque,
                              onTap: () =>
                                  _showOverflowSheet(context, appointments),
                              child: _OverflowTile(count: appointments.length),
                            ),
                          ),
                        ),
                      },
                    ),
                  if (isToday)
                    ValueListenableBuilder<DateTime>(
                      valueListenable: nowNotifier,
                      builder: (_, now, child) => _CurrentTimeMarker(
                        now: now,
                        layout: layout,
                        theme: theme,
                      ),
                    ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

/// Shared bottom sheet listing hidden appointments (used by both the in-grid
/// and outside-hours overflow cells). [appointments] is rendered in the given
/// order — callers pre-sort as needed.
void _showAppointmentOverflowSheet(
  BuildContext context,
  List<Appointment> appointments, {
  required void Function(Appointment) onAppointmentTap,
  required bool Function(Appointment) isCrossedOut,
  required String Function(Appointment) subtitle,
}) {
  showDetailsBottomSheet(
    context,
    children: (sheetContext) {
      final tiles = <Widget>[];
      for (var i = 0; i < appointments.length; i++) {
        if (i > 0) tiles.add(const Divider(height: 1));
        final apt = appointments[i];
        tiles.add(
          ListTile(
            leading: Container(
              width: 12,
              height: 12,
              decoration: BoxDecoration(
                color: apt.color,
                borderRadius: BorderRadius.circular(3),
              ),
            ),
            title: Text(
              apt.subject,
              style: isCrossedOut(apt)
                  ? const TextStyle(decoration: TextDecoration.lineThrough)
                  : null,
            ),
            subtitle: Text(subtitle(apt)),
            onTap: () {
              Navigator.of(sheetContext).pop();
              onAppointmentTap(apt);
            },
          ),
        );
      }
      return tiles;
    },
  );
}

class _CurrentTimeMarker extends StatelessWidget {
  final DateTime now;
  final PeriodLayout layout;
  final ThemeData theme;

  const _CurrentTimeMarker({
    required this.now,
    required this.layout,
    required this.theme,
  });

  @override
  Widget build(BuildContext context) {
    final periods = layout.periods;
    if (periods.isEmpty) return const SizedBox.shrink();
    final tMin = now.hour * 60 + now.minute;
    final firstStart =
        periods.first.start.hour * 60 + periods.first.start.minute;
    final lastEnd = periods.last.end.hour * 60 + periods.last.end.minute;
    if (tMin < firstStart || tMin > lastEnd) return const SizedBox.shrink();

    final y = layout.yOfDateTime(now);

    return AnimatedPositioned(
      duration: const Duration(milliseconds: 400),
      curve: Curves.easeInOut,
      top: y - 1,
      left: 0,
      right: 0,
      child: IgnorePointer(
        child: Stack(
          clipBehavior: Clip.none,
          children: [
            Container(height: 2, color: theme.colorScheme.primary),
            Positioned(
              top: -3,
              left: -4,
              child: Container(
                width: 8,
                height: 8,
                decoration: BoxDecoration(
                  color: theme.colorScheme.primary,
                  shape: BoxShape.circle,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _OverflowTile extends StatelessWidget {
  final int count;
  const _OverflowTile({required this.count});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final scheme = theme.colorScheme;
    const radius = BorderRadius.all(Radius.circular(7));

    return Padding(
      padding: const EdgeInsets.all(1),
      child: Stack(
        children: [
          // Stacked-cards effect: a darker layer peeks out below the front card.
          Positioned(
            top: 4,
            left: 2,
            right: 2,
            bottom: 0,
            child: DecoratedBox(
              decoration: BoxDecoration(
                borderRadius: radius,
                color: scheme.secondaryContainer.withAlpha(120),
              ),
            ),
          ),
          Positioned(
            top: 0,
            left: 0,
            right: 0,
            bottom: 4,
            child: Container(
              decoration: BoxDecoration(
                borderRadius: radius,
                color: scheme.secondaryContainer,
              ),
              alignment: Alignment.center,
              child: FittedBox(
                fit: BoxFit.scaleDown,
                child: Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 6,
                    vertical: 2,
                  ),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(
                        Icons.unfold_more_rounded,
                        size: 18,
                        color: scheme.onSecondaryContainer,
                      ),
                      Text(
                        '+$count',
                        style: theme.textTheme.titleSmall?.copyWith(
                          color: scheme.onSecondaryContainer,
                          fontWeight: FontWeight.w700,
                          height: 1.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
