mirrored_repositories/enough_mail_app/lib/src
import 'package:enough_icalendar/enough_icalendar.dart';
import 'package:enough_platform_widgets/enough_platform_widgets.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../account/provider.dart';
import '../localization/app_localizations.g.dart';
import '../localization/extension.dart';
import '../logger.dart';
import '../util/datetime.dart';
import '../util/modal_bottom_sheet_helper.dart';

/// A widget to compose an iCalendar appointment.
class IcalComposer extends StatefulHookConsumerWidget {
  /// Creates a new [IcalComposer].
  const IcalComposer({super.key, required this.appointment});

  /// The appointment to edit.
  final VCalendar appointment;
  @override
  ConsumerState<IcalComposer> createState() => _IcalComposerState();

  /// Creates a new appointment or edits an existing one.
  static Future<VCalendar?> createOrEditAppointment(
    BuildContext context,
    WidgetRef ref, {
    VCalendar? appointment,
  }) async {
    final localizations = ref.text;
    // final iconService = IconService.instance;
    final account = ref.read(currentRealAccountProvider);
    if (account == null) {
      logger.e('Unable to determine current real account');

      return null;
    }
    final now = DateTime.now();
    final start = DateTime(now.year, now.month, now.day, (now.hour + 1) % 24);
    final end = DateTime(start.year, start.month, start.day, start.hour, 30);
    final editAppointment = appointment ??
        VCalendar.createEvent(
          start: start,
          end: end,
          organizerEmail: account.email,
        );
    final result = await ModelBottomSheetHelper.showModalBottomSheet<bool>(
      context,
      editAppointment.summary ?? localizations.composeAppointmentTitle,
      IcalComposer(appointment: editAppointment),
    );

    if (result ?? false) {
      _IcalComposerState._current.apply();
      appointment = editAppointment;
    }

    return appointment;
  }
}

class _IcalComposerState extends ConsumerState<IcalComposer> {
  static late _IcalComposerState _current;
  final TextEditingController _summaryController = TextEditingController();
  final TextEditingController _descriptionController = TextEditingController();
  final TextEditingController _locationController = TextEditingController();
  // final List<MailAddress> _participants = <MailAddress>[];
  // late MailAccount _organizerAccount;
  late VEvent _event;
  DateTime? _previousStart;
  DateTime? _previousEnd;

  @override
  void initState() {
    _current = this;
    super.initState();
    final ev = widget.appointment.event;
    if (ev != null) {
      _event = ev;
      _summaryController.text = ev.summary ?? '';
      _descriptionController.text = ev.description ?? '';
      _locationController.text = ev.location ?? '';
    } else {
      _event = VEvent(parent: widget.appointment);
      widget.appointment.children.add(_event);
    }
  }

  @override
  void dispose() {
    apply();
    _summaryController.dispose();
    _descriptionController.dispose();
    _locationController.dispose();
    super.dispose();
  }

  void apply() {
    _event
      ..summary = _summaryController.text
      ..description = _descriptionController.text.isNotEmpty
          ? _descriptionController.text
          : null
      ..location =
          _locationController.text.isNotEmpty ? _locationController.text : null;
  }

  @override
  Widget build(BuildContext context) {
    final localizations = ref.text;
    final end = _event.end;
    final start = _event.start ?? DateTime.now();
    final isAllDay = _event.isAllDayEvent ?? false;
    final recurrenceRule = _event.recurrenceRule;
    final theme = Theme.of(context);

    return Material(
      child: Padding(
        padding: const EdgeInsets.all(8),
        child: Column(
          children: [
            DecoratedPlatformTextField(
              controller: _summaryController,
              decoration: InputDecoration(
                labelText: localizations.icalendarLabelSummary,
              ),
              cupertinoAlignLabelOnTop: true,
            ),
            DecoratedPlatformTextField(
              controller: _descriptionController,
              keyboardType: TextInputType.multiline,
              minLines: 3,
              maxLines: 8,
              decoration: InputDecoration(
                labelText: localizations.icalendarLabelDescription,
              ),
              cupertinoAlignLabelOnTop: true,
            ),
            DecoratedPlatformTextField(
              controller: _locationController,
              decoration: InputDecoration(
                labelText: localizations.icalendarLabelLocation,
              ),
              cupertinoAlignLabelOnTop: true,
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(8, 16, 8, 0),
              child: Text(
                localizations.icalendarLabelStart,
                style: theme.textTheme.bodySmall,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8),
              child: _DateTimePicker(
                dateTime: start,
                onlyDate: isAllDay,
                onChanged: (dateTime) {
                  if (end != null) {
                    final diff = end.difference(start);
                    _event.end = dateTime.add(diff);
                  }
                  setState(() {
                    _event.start = dateTime;
                  });
                },
              ),
            ),
            if (!isAllDay) ...[
              Padding(
                padding: const EdgeInsets.fromLTRB(8, 16, 8, 0),
                child: Text(
                  localizations.icalendarLabelEnd,
                  style: theme.textTheme.bodySmall,
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                child: _DateTimePicker(
                  dateTime: end,
                  onChanged: (dateTime) {
                    setState(() {
                      _event.end = dateTime;
                    });
                  },
                ),
              ),
            ],
            PlatformCheckboxListTile(
              value: isAllDay,
              title: Text(localizations.composeAppointmentLabelAllDayEvent),
              onChanged: (value) {
                if (value ?? false) {
                  _previousStart = start;
                  _previousEnd = end;
                  _event
                    ..end = null
                    ..start = DateTime(start.year, start.month, start.day)
                    ..duration = IsoDuration(days: 1);
                } else {
                  _event
                    ..duration = null
                    ..start = _previousStart
                    ..end = _previousEnd;
                }
                setState(() {
                  _event.isAllDayEvent = value;
                });
              },
            ),
            const Divider(),
            PlatformListTile(
              title: Text(localizations.composeAppointmentLabelRepeat),
              trailing: recurrenceRule == null
                  ? Text(localizations.composeAppointmentLabelRepeatOptionNever)
                  : null,
              subtitle: recurrenceRule == null
                  ? null
                  : Text(
                      recurrenceRule.toHumanReadableText(
                        languageCode: localizations.localeName,
                        startDate: start,
                      ),
                      style: theme.textTheme.bodySmall,
                    ),
              onTap: () async {
                final result = await _RecurrenceComposer.createOrEditRecurrence(
                  ref,
                  context,
                  recurrenceRule,
                  start,
                );
                setState(() {
                  _event.recurrenceRule = result;
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

enum _RepeatFrequency { never, daily, weekly, monthly, yearly }

extension _ExtensionRepeatFrequency on _RepeatFrequency {
  RecurrenceFrequency? get recurrenceFrequency {
    switch (this) {
      case _RepeatFrequency.never:
        return null;
      case _RepeatFrequency.daily:
        return RecurrenceFrequency.daily;
      case _RepeatFrequency.weekly:
        return RecurrenceFrequency.weekly;
      case _RepeatFrequency.monthly:
        return RecurrenceFrequency.monthly;
      case _RepeatFrequency.yearly:
        return RecurrenceFrequency.yearly;
    }
  }

  String localization(AppLocalizations localizations) {
    switch (this) {
      case _RepeatFrequency.never:
        return localizations.composeAppointmentLabelRepeatOptionNever;
      case _RepeatFrequency.daily:
        return localizations.composeAppointmentLabelRepeatOptionDaily;
      case _RepeatFrequency.weekly:
        return localizations.composeAppointmentLabelRepeatOptionWeekly;
      case _RepeatFrequency.monthly:
        return localizations.composeAppointmentLabelRepeatOptionMonthly;
      case _RepeatFrequency.yearly:
        return localizations.composeAppointmentLabelRepeatOptionYearly;
    }
  }
}

extension _ExtensionRecurrenceFrequency on RecurrenceFrequency {
  _RepeatFrequency get repeatFrequency {
    switch (this) {
      case RecurrenceFrequency.secondly:
      case RecurrenceFrequency.minutely:
      case RecurrenceFrequency.hourly:
      case RecurrenceFrequency.daily:
        return _RepeatFrequency.daily;
      case RecurrenceFrequency.weekly:
        return _RepeatFrequency.weekly;
      case RecurrenceFrequency.monthly:
        return _RepeatFrequency.monthly;
      case RecurrenceFrequency.yearly:
        return _RepeatFrequency.yearly;
    }
  }

  IsoDuration? get recommendedUntil {
    switch (this) {
      case RecurrenceFrequency.secondly:
      case RecurrenceFrequency.minutely:
      case RecurrenceFrequency.hourly:
        return null;
      case RecurrenceFrequency.daily:
        return IsoDuration(months: 3);
      case RecurrenceFrequency.weekly:
        return IsoDuration(months: 6);
      case RecurrenceFrequency.monthly:
        return IsoDuration(years: 1);
      case RecurrenceFrequency.yearly:
        return null;
    }
  }
}

class _DateTimePicker extends ConsumerWidget {
  const _DateTimePicker({
    required this.dateTime,
    required this.onChanged,
    this.onlyDate = false,
  });
  final DateTime? dateTime;
  final void Function(DateTime newDateTime) onChanged;
  final bool onlyDate;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final localizations = ref.text;
    final dt = dateTime;

    return Row(
      children: [
        // set date button:
        PlatformTextButton(
          child: Text(
            dt == null
                ? localizations.composeAppointmentLabelDay
                : ref.formatDate(dt.toLocal(), useLongFormat: true),
          ),
          onPressed: () async {
            FocusScope.of(context).unfocus();
            final initialDate = dt ?? DateTime.now();
            final firstDate = DateTime.now();
            final lastDate =
                DateTime(firstDate.year + 10, firstDate.month, firstDate.day);
            final newStartDate = await showPlatformDatePicker(
              context: context,
              initialDate: initialDate,
              firstDate: firstDate,
              lastDate: lastDate,
            );
            if (newStartDate != null) {
              final withTimeOfDay =
                  newStartDate.withTimeOfDay(initialDate.toTimeOfDay());
              onChanged(withTimeOfDay);
            }
          },
        ),
        if (!onlyDate)
          // set time button:
          PlatformTextButton(
            child: Text(
              dt == null
                  ? localizations.composeAppointmentLabelTime
                  : context.formatTimeOfDay(
                      TimeOfDay.fromDateTime(dt.toLocal()),
                    ),
            ),
            onPressed: () async {
              FocusScope.of(context).unfocus();
              final initialDateTime = dt ?? DateTime.now();
              final initialTime = initialDateTime.toTimeOfDay();
              final newStartTime = await showPlatformTimePicker(
                context: context,
                initialTime: initialTime,
              );

              if (newStartTime != null) {
                final withTimeOfDay =
                    initialDateTime.withTimeOfDay(newStartTime);
                onChanged(withTimeOfDay);
              }
            },
          ),
      ],
    );
  }
}

class _RecurrenceComposer extends StatefulHookConsumerWidget {
  const _RecurrenceComposer({
    this.recurrenceRule,
    required this.startDate,
  });
  final Recurrence? recurrenceRule;
  final DateTime startDate;

  @override
  ConsumerState<_RecurrenceComposer> createState() =>
      _RecurrenceComposerState();

  static Future<Recurrence?> createOrEditRecurrence(
    WidgetRef ref,
    BuildContext context,
    Recurrence? recurrenceRule,
    DateTime startDate,
  ) async {
    final localizations = ref.text;
    // final iconService = IconService.instance;

    final result = await ModelBottomSheetHelper.showModalBottomSheet<bool>(
      context,
      localizations.composeAppointmentLabelRepeat,
      _RecurrenceComposer(
        recurrenceRule: recurrenceRule,
        startDate: startDate,
      ),
    );

    return result ?? false
        ? _RecurrenceComposerState._currentState._recurrenceRule
        : recurrenceRule;
  }
}

class _RecurrenceComposerState extends ConsumerState<_RecurrenceComposer> {
  static late _RecurrenceComposerState _currentState;
  Recurrence? _recurrenceRule;
  _RepeatFrequency _repeatFrequency = _RepeatFrequency.never;
  DateTime? _recommendationDate;

  @override
  void initState() {
    _currentState = this;
    super.initState();
    final rule = widget.recurrenceRule;
    _recurrenceRule = rule;
    if (rule != null) {
      _repeatFrequency = rule.frequency.repeatFrequency;
    }
  }

  @override
  Widget build(BuildContext context) {
    final localizations = ref.text;

    final rule = _recurrenceRule;

    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Padding(
                padding: const EdgeInsets.all(8),
                child: Text(
                  localizations.composeAppointmentRecurrenceFrequencyLabel,
                ),
              ),
              PlatformDropdownButton<_RepeatFrequency>(
                items: _RepeatFrequency.values
                    .map((rf) => DropdownMenuItem<_RepeatFrequency>(
                          value: rf,
                          child: Text(rf.localization(localizations)),
                        ))
                    .toList(),
                onChanged: (freq) {
                  if (freq == null || freq == _RepeatFrequency.never) {
                    setState(() {
                      _repeatFrequency = _RepeatFrequency.never;
                      _recurrenceRule = null;
                    });

                    return;
                  }
                  DateTime? until;
                  final duration = freq.recurrenceFrequency?.recommendedUntil;
                  if (duration == null) {
                    _recommendationDate = null;
                  } else {
                    until = duration.addTo(widget.startDate);
                    _recommendationDate = until;
                  }
                  var newRule = (rule != null)
                      ? rule.copyWith(
                          frequency: freq.recurrenceFrequency,
                          until: until,
                          copyByRules: false,
                          copyUntil: false,
                        )
                      : Recurrence(
                          freq.recurrenceFrequency ?? RecurrenceFrequency.daily,
                          until: until,
                        );
                  if (newRule.frequency == RecurrenceFrequency.monthly) {
                    final monthly = _DayOfMonthSelector.updateMonthlyRecurrence(
                      newRule,
                      widget.startDate,
                    );
                    if (monthly != null) {
                      newRule = monthly;
                    }
                  }
                  setState(() {
                    _repeatFrequency = freq;
                    _recurrenceRule = newRule;
                  });
                },
                value: _repeatFrequency,
              ),
            ],
          ),
          if (rule != null) ...[
            Row(
              children: [
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Text(
                    localizations.composeAppointmentRecurrenceIntervalLabel,
                  ),
                ),
                PlatformDropdownButton<int>(
                  items: List.generate(
                    10,
                    (index) => DropdownMenuItem<int>(
                      value: index + 1,
                      child: Text('${index + 1}'),
                    ),
                  ),
                  onChanged: (interval) {
                    setState(() {
                      _recurrenceRule = rule.copyWith(interval: interval);
                    });
                  },
                  value: rule.interval,
                ),
              ],
            ),
            if (rule.frequency == RecurrenceFrequency.weekly) ...[
              Padding(
                padding: const EdgeInsets.all(8),
                child:
                    Text(localizations.composeAppointmentRecurrenceDaysLabel),
              ),
              _WeekDaySelector(
                recurrence: rule,
                startDate: widget.startDate,
                onChanged: (rules) {
                  Recurrence value;
                  value = rules == null
                      ? rule.copyWith(copyByRules: false)
                      : rule.copyWith(byWeekDay: rules);
                  setState(() {
                    _recurrenceRule = value;
                  });
                },
              ),
            ] else if (rule.frequency == RecurrenceFrequency.monthly) ...[
              Padding(
                padding: const EdgeInsets.all(8),
                child:
                    Text(localizations.composeAppointmentRecurrenceDaysLabel),
              ),
              _DayOfMonthSelector(
                recurrence: rule,
                startDate: widget.startDate,
                onChanged: (value) {
                  setState(() {
                    _recurrenceRule = value;
                  });
                },
              ),
            ],
            PlatformListTile(
              title: Text(localizations.composeAppointmentRecurrenceUntilLabel),
              trailing: Text(
                rule.until == null
                    ? localizations
                        .composeAppointmentRecurrenceUntilOptionUnlimited
                    : rule.until == _recommendationDate
                        ? localizations
                            .composeAppointmentRecurrenceUntilOptionRecommended(
                            ref.formatIsoDuration(
                              rule.frequency.recommendedUntil ?? IsoDuration(),
                            ),
                          )
                        : ref.formatDate(
                            rule.until,
                            useLongFormat: true,
                          ),
              ),
              onTap: () async {
                final until = await _UntilComposer.createOrEditUntil(
                  context,
                  ref,
                  widget.startDate,
                  rule.until,
                  rule.frequency.recommendedUntil,
                );
                final newRule = (until == null)
                    ? rule.copyWithout(RecurrenceAttribute.until)
                    : rule.copyWith(until: until);
                setState(() {
                  _recurrenceRule = newRule;
                });
              },
            ),
            const Divider(),
            Padding(
              padding: const EdgeInsets.all(8),
              child: Text(rule.toHumanReadableText(
                languageCode: localizations.localeName,
                startDate: widget.startDate,
              )),
            ),
          ],
        ],
      ),
    );
  }
}

class _WeekDaySelector extends StatefulHookConsumerWidget {
  const _WeekDaySelector({
    required this.recurrence,
    required this.onChanged,
    required this.startDate,
  });
  final Recurrence recurrence;
  final DateTime startDate;
  final void Function(List<ByDayRule>? rules) onChanged;

  @override
  ConsumerState<_WeekDaySelector> createState() => _WeekDaySelectorState();
}

class _WeekDaySelectorState extends ConsumerState<_WeekDaySelector> {
  late List<WeekDay> _weekdays;
  final _selectedDays = <bool>[false, false, false, false, false, false, false];
  @override
  void initState() {
    super.initState();
    _weekdays = ref.formatWeekDays(abbreviate: true);
    final byWeekDays = widget.recurrence.byWeekDay;
    if (byWeekDays != null) {
      final int firstDayOfWeek = ref.firstDayOfWeek;
      for (int i = 0; i < 7; i++) {
        final day = ((firstDayOfWeek + i) <= 7)
            ? (firstDayOfWeek + i)
            : ((firstDayOfWeek + i) - 7);
        final bool isSelected =
            byWeekDays.any((dayRule) => dayRule.weekday == day);
        _selectedDays[i] = isSelected;
      }
    }
    _selectStartDateWeekDay();
  }

  void _selectStartDateWeekDay() {
    final startDateWeekDay = widget.startDate.weekday;
    final index =
        _weekdays.indexWhere((weekDay) => weekDay.day == startDateWeekDay);
    _selectedDays[index] = true;
  }

  void _toggle(int index) {
    final day = _weekdays[index].day;
    var isSelected = !_selectedDays[index];
    var rules = widget.recurrence.byWeekDay;
    if (isSelected) {
      if (rules == null) {
        rules = [ByDayRule(day), ByDayRule(widget.startDate.weekday)];
      } else {
        rules.add(ByDayRule(day));
      }
    } else {
      if (rules != null) {
        rules.removeWhere((rule) => rule.weekday == day);
        if (rules.isEmpty ||
            rules.length == 1 && rules[0].weekday == widget.startDate.weekday) {
          rules = null;
          // re-select weekday from start day:
          _selectStartDateWeekDay();
        }
      } else if (day == widget.startDate.weekday) {
        isSelected = true;
      }
    }
    widget.onChanged(rules);
    setState(() => _selectedDays[index] = isSelected);
  }

  @override
  Widget build(BuildContext context) => FittedBox(
        child: PlatformToggleButtons(
          isSelected: _selectedDays,
          onPressed: _toggle,
          children: _weekdays
              .map((day) => Padding(
                    padding: const EdgeInsets.all(8),
                    child: Text(day.name),
                  ))
              .toList(),
        ),
      );
}

enum _DayOfMonthOption { dayOfMonth, dayInNumberedWeek }

class _DayOfMonthSelector extends StatefulHookConsumerWidget {
  const _DayOfMonthSelector({
    required this.recurrence,
    required this.startDate,
    required this.onChanged,
  });
  final Recurrence recurrence;
  final DateTime startDate;
  final void Function(Recurrence recurrence) onChanged;

  @override
  ConsumerState<_DayOfMonthSelector> createState() =>
      _DayOfMonthSelectorState();

  static Recurrence? updateMonthlyRecurrence(
    Recurrence recurrence,
    DateTime startDate,
  ) {
    if (recurrence.hasByMonthDay || recurrence.hasByWeekDay) {
      return null;
    }
    final day = startDate.day;
    final weekday = startDate.weekday;
    var week = (day / 7).ceil();
    if (week > 3) {
      // is the last or the second last weekday?
      final daysInMonth = DateTime(startDate.year, startDate.month + 1, 0).day;
      week = -((daysInMonth - day) / 7).ceil();
    }
    final rule = ByDayRule(weekday, week: week);

    return recurrence.copyWith(byWeekDay: [rule], copyByRules: false);
  }
}

class _DayOfMonthSelectorState extends ConsumerState<_DayOfMonthSelector> {
  late _DayOfMonthOption _option;
  ByDayRule? _byDayRule;
  WeekDay? _currentWeekday;
  late List<WeekDay> _weekdays;

  @override
  void initState() {
    super.initState();
    _weekdays = ref.formatWeekDays();
    if (widget.recurrence.hasByMonthDay) {
      _option = _DayOfMonthOption.dayOfMonth;
    } else {
      var recurrence = widget.recurrence;
      if (!widget.recurrence.hasByWeekDay) {
        recurrence = _DayOfMonthSelector.updateMonthlyRecurrence(
              recurrence,
              widget.startDate,
            ) ??
            recurrence;
      }
      _option = _DayOfMonthOption.dayInNumberedWeek;
      final rule = recurrence.byWeekDay?.first;
      _byDayRule = rule;
      _currentWeekday = _weekdays.firstWhere((wd) => wd.day == rule?.weekday);
    }
  }

  @override
  Widget build(BuildContext context) {
    final localizations = ref.text;
    final rule = _byDayRule;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        PlatformRadioListTile<_DayOfMonthOption>(
          groupValue: _option,
          value: _DayOfMonthOption.dayOfMonth,
          title: Text(
            localizations.composeAppointmentRecurrenceMonthlyOnDayOfMonth(
              widget.startDate.day,
            ),
          ),
          onChanged: (value) {
            if (value == null) {
              return;
            }

            setState(() {
              _option = value;
            });
            widget.onChanged(
              widget.recurrence.copyWith(
                byMonthDay: [widget.startDate.day],
                copyByRules: false,
              ),
            );
          },
        ),
        PlatformRadioListTile<_DayOfMonthOption>(
          groupValue: _option,
          value: _DayOfMonthOption.dayInNumberedWeek,
          title:
              Text(localizations.composeAppointmentRecurrenceMonthlyOnWeekDay),
          onChanged: (value) {
            if (value == null) {
              return;
            }
            if (_byDayRule == null) {
              final recurrence = _DayOfMonthSelector.updateMonthlyRecurrence(
                    widget.recurrence.copyWith(copyByRules: false),
                    widget.startDate,
                  ) ??
                  widget.recurrence.copyWith(
                    byWeekDay: [ByDayRule(widget.startDate.weekday)],
                  );
              final rule = recurrence.byWeekDay?.first;
              _byDayRule = rule;
              _currentWeekday =
                  _weekdays.firstWhere((wd) => wd.day == rule?.weekday);
              widget.onChanged(recurrence);
            }
            setState(() {
              _option = value;
            });
          },
        ),
        if (_option == _DayOfMonthOption.dayInNumberedWeek && rule != null) ...[
          Padding(
            padding: const EdgeInsets.fromLTRB(32, 8, 8, 32),
            child: Row(
              children: [
                PlatformDropdownButton<int>(
                  items: [
                    DropdownMenuItem<int>(
                      value: 1,
                      child:
                          Text(localizations.composeAppointmentRecurrenceFirst),
                    ),
                    DropdownMenuItem<int>(
                      value: 2,
                      child: Text(
                        localizations.composeAppointmentRecurrenceSecond,
                      ),
                    ),
                    DropdownMenuItem<int>(
                      value: 3,
                      child: Text(
                        localizations.composeAppointmentRecurrenceThird,
                      ),
                    ),
                    DropdownMenuItem<int>(
                      value: -1,
                      child:
                          Text(localizations.composeAppointmentRecurrenceLast),
                    ),
                    DropdownMenuItem<int>(
                      value: -2,
                      child: Text(
                        localizations.composeAppointmentRecurrenceSecondLast,
                      ),
                    ),
                  ],
                  value: rule.week,
                  onChanged: (value) {
                    final newRule = ByDayRule(rule.weekday, week: value);
                    _byDayRule = newRule;
                    final recurrence =
                        widget.recurrence.copyWith(byWeekDay: [newRule]);
                    widget.onChanged(recurrence);
                  },
                ),
                const Padding(
                  padding: EdgeInsets.all(8),
                ),
                PlatformDropdownButton<WeekDay>(
                  items: _weekdays
                      .map((wd) => DropdownMenuItem<WeekDay>(
                            value: wd,
                            child: Text(wd.name),
                          ))
                      .toList(),
                  value: _currentWeekday,
                  onChanged: (value) {
                    if (value == null) {
                      return;
                    }
                    final newRule = ByDayRule(value.day, week: rule.week);
                    _byDayRule = newRule;
                    final recurrence =
                        widget.recurrence.copyWith(byWeekDay: [newRule]);
                    widget.onChanged(recurrence);
                  },
                ),
              ],
            ),
          ),
        ],
      ],
    );
  }
}

class _UntilComposer extends StatefulHookConsumerWidget {
  const _UntilComposer({
    required this.start,
    this.until,
    this.recommendation,
  });

  final DateTime start;
  final DateTime? until;
  final IsoDuration? recommendation;

  @override
  ConsumerState<_UntilComposer> createState() => _UntilComposerState();

  static Future<DateTime?> createOrEditUntil(
    BuildContext context,
    WidgetRef ref,
    DateTime start,
    DateTime? until,
    IsoDuration? recommendation,
  ) async {
    final localizations = ref.text;
    // final iconService = IconService.instance;
    final result = await ModelBottomSheetHelper.showModalBottomSheet<bool>(
      context,
      localizations.composeAppointmentRecurrenceUntilLabel,
      _UntilComposer(
        start: start,
        until: until,
        recommendation: recommendation,
      ),
    );

    return (result ?? false) ? _UntilComposerState._currentState._until : until;
  }
}

class _UntilComposerState extends ConsumerState<_UntilComposer> {
  static late _UntilComposerState _currentState;
  late _UntilOption _option;
  DateTime? _recommendationDate;
  DateTime? _until;

  @override
  void initState() {
    super.initState();
    _currentState = this;
    _until = widget.until;
    final recommendation = widget.recommendation;
    if (recommendation != null) {
      _recommendationDate = recommendation.addTo(widget.start);
    }
    if (_until == null) {
      _option = _UntilOption.unlimited;
    } else if (_until == _recommendationDate) {
      _option = _UntilOption.recommendation;
    } else {
      _option = _UntilOption.date;
    }
  }

  @override
  Widget build(BuildContext context) {
    // final i18nService = locator<I18nService>();
    final localizations = ref.text;
    final theme = Theme.of(context);

    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (final value in _UntilOption.values)
            if (_recommendationDate != null ||
                value != _UntilOption.recommendation)
              PlatformRadioListTile<_UntilOption>(
                groupValue: _option,
                value: value,
                onChanged: _onChanged,
                title: Text(
                  value.localization(
                    ref,
                    localizations,
                    widget.recommendation,
                  ),
                ),
              ),
          if (_option == _UntilOption.date) ...[
            Padding(
              padding: const EdgeInsets.fromLTRB(8, 16, 8, 0),
              child: Text(
                localizations.composeAppointmentRecurrenceUntilLabel,
                style: theme.textTheme.bodySmall,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8),
              child: _DateTimePicker(
                dateTime: _until,
                onlyDate: true,
                onChanged: (dateTime) {
                  setState(() {
                    _until = dateTime;
                  });
                },
              ),
            ),
          ],
        ],
      ),
    );
  }

  void _onChanged(_UntilOption? value) {
    if (value != null) {
      switch (value) {
        case _UntilOption.unlimited:
          _until = null;
          break;
        case _UntilOption.recommendation:
          _until = _recommendationDate;
          break;
        case _UntilOption.date:
          break;
      }
      setState(() {
        _option = value;
      });
    }
  }
}

enum _UntilOption { unlimited, recommendation, date }

extension _ExtensionUntilOption on _UntilOption {
  String localization(
    WidgetRef ref,
    AppLocalizations localizations,
    IsoDuration? recommendation,
  ) {
    switch (this) {
      case _UntilOption.unlimited:
        return localizations.composeAppointmentRecurrenceUntilOptionUnlimited;
      case _UntilOption.recommendation:
        final duration =
            recommendation == null ? '' : ref.formatIsoDuration(recommendation);
        return localizations
            .composeAppointmentRecurrenceUntilOptionRecommended(duration);
      case _UntilOption.date:
        return localizations
            .composeAppointmentRecurrenceUntilOptionSpecificDate;
    }
  }
}
