import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tasq/utils/app_time.dart';
import 'package:geolocator/geolocator.dart';
import 'package:timezone/timezone.dart' as tz;

import '../../models/duty_schedule.dart';
import '../../models/profile.dart';
import '../../models/swap_request.dart';
import '../../providers/profile_provider.dart';
import '../../providers/workforce_provider.dart';
import '../../widgets/responsive_body.dart';
import '../../theme/app_surfaces.dart';

class WorkforceScreen extends ConsumerWidget {
  const WorkforceScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final profileAsync = ref.watch(currentProfileProvider);
    final role = profileAsync.valueOrNull?.role ?? 'standard';
    final isAdmin = role == 'admin' || role == 'dispatcher';

    return ResponsiveBody(
      child: LayoutBuilder(
        builder: (context, constraints) {
          final isWide = constraints.maxWidth >= 980;
          final schedulePanel = _SchedulePanel(isAdmin: isAdmin);
          final swapsPanel = _SwapRequestsPanel(isAdmin: isAdmin);
          final generatorPanel = _ScheduleGeneratorPanel(enabled: isAdmin);

          if (isWide) {
            return Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(flex: 3, child: schedulePanel),
                const SizedBox(width: 16),
                Expanded(
                  flex: 2,
                  child: Column(
                    children: [
                      if (isAdmin) generatorPanel,
                      if (isAdmin) const SizedBox(height: 16),
                      Expanded(child: swapsPanel),
                    ],
                  ),
                ),
              ],
            );
          }

          return DefaultTabController(
            length: isAdmin ? 3 : 2,
            child: Column(
              children: [
                const SizedBox(height: 8),
                TabBar(
                  tabs: [
                    const Tab(text: 'Schedule'),
                    const Tab(text: 'Swaps'),
                    if (isAdmin) const Tab(text: 'Generator'),
                  ],
                ),
                const SizedBox(height: 8),
                Expanded(
                  child: TabBarView(
                    children: [
                      schedulePanel,
                      swapsPanel,
                      if (isAdmin) generatorPanel,
                    ],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

class _SchedulePanel extends ConsumerWidget {
  const _SchedulePanel({required this.isAdmin});

  final bool isAdmin;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final schedulesAsync = ref.watch(dutySchedulesProvider);
    final profilesAsync = ref.watch(profilesProvider);
    final currentUserId = ref.watch(currentUserIdProvider);

    return schedulesAsync.when(
      data: (schedules) {
        if (schedules.isEmpty) {
          return const Center(child: Text('No schedules yet.'));
        }

        final Map<String, Profile> profileById = {
          for (final profile in profilesAsync.valueOrNull ?? [])
            profile.id: profile,
        };
        final grouped = <DateTime, List<DutySchedule>>{};
        for (final schedule in schedules) {
          final day = DateTime(
            schedule.startTime.year,
            schedule.startTime.month,
            schedule.startTime.day,
          );
          grouped.putIfAbsent(day, () => []).add(schedule);
        }

        final days = grouped.keys.toList()..sort();

        return ListView.builder(
          padding: const EdgeInsets.only(bottom: 24),
          itemCount: days.length,
          itemBuilder: (context, index) {
            final day = days[index];
            final items = grouped[day]!
              ..sort((a, b) => a.startTime.compareTo(b.startTime));
            return Padding(
              padding: const EdgeInsets.only(bottom: 12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    AppTime.formatDate(day),
                    style: Theme.of(context).textTheme.titleMedium?.copyWith(
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  const SizedBox(height: 8),
                  ...items.map(
                    (schedule) => _ScheduleTile(
                      schedule: schedule,
                      displayName: _scheduleName(
                        profileById,
                        schedule,
                        isAdmin,
                      ),
                      relieverLabels: _relieverLabelsFromIds(
                        schedule.relieverIds,
                        profileById,
                      ),
                      isMine: schedule.userId == currentUserId,
                    ),
                  ),
                ],
              ),
            );
          },
        );
      },
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, _) =>
          Center(child: Text('Failed to load schedules: $error')),
    );
  }

  String _scheduleName(
    Map<String, Profile> profileById,
    DutySchedule schedule,
    bool isAdmin,
  ) {
    if (!isAdmin) {
      return _shiftLabel(schedule.shiftType);
    }
    final profile = profileById[schedule.userId];
    final name = profile?.fullName.isNotEmpty == true
        ? profile!.fullName
        : schedule.userId;
    return '${_shiftLabel(schedule.shiftType)} · $name';
  }

  String _shiftLabel(String value) {
    switch (value) {
      case 'am':
        return 'AM Duty';
      case 'pm':
        return 'PM Duty';
      case 'on_call':
        return 'On Call';
      case 'normal':
        return 'Normal';
      case 'weekend':
        return 'Weekend';
      default:
        return value;
    }
  }

  List<String> _relieverLabelsFromIds(
    List<String> relieverIds,
    Map<String, Profile> profileById,
  ) {
    if (relieverIds.isEmpty) return const [];
    return relieverIds
        .map(
          (id) => profileById[id]?.fullName.isNotEmpty == true
              ? profileById[id]!.fullName
              : id,
        )
        .toList();
  }
}

class _ScheduleTile extends ConsumerWidget {
  const _ScheduleTile({
    required this.schedule,
    required this.displayName,
    required this.relieverLabels,
    required this.isMine,
  });

  final DutySchedule schedule;
  final String displayName;
  final List<String> relieverLabels;
  final bool isMine;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentUserId = ref.watch(currentUserIdProvider);
    final swaps = ref.watch(swapRequestsProvider).valueOrNull ?? [];
    final now = AppTime.now();
    final isPast = schedule.startTime.isBefore(now);
    final canCheckIn =
        isMine &&
        schedule.checkInAt == null &&
        (schedule.status == 'scheduled' || schedule.status == 'late') &&
        now.isAfter(schedule.startTime.subtract(const Duration(hours: 2))) &&
        now.isBefore(schedule.endTime);
    final hasRequestedSwap = swaps.any(
      (swap) =>
          swap.requesterScheduleId == schedule.id &&
          swap.requesterId == currentUserId &&
          swap.status == 'pending',
    );
    final canRequestSwap = isMine && schedule.status != 'absent' && !isPast;

    return Card(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        displayName,
                        style: Theme.of(context).textTheme.titleSmall?.copyWith(
                          fontWeight: FontWeight.w700,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        '${AppTime.formatTime(schedule.startTime)} - ${AppTime.formatTime(schedule.endTime)}',
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                      const SizedBox(height: 4),
                      Text(
                        _statusLabel(schedule.status),
                        style: Theme.of(context).textTheme.bodySmall?.copyWith(
                          color: _statusColor(context, schedule.status),
                        ),
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    if (canCheckIn)
                      FilledButton.icon(
                        onPressed: () => _handleCheckIn(context, ref, schedule),
                        icon: const Icon(Icons.location_on),
                        label: const Text('Check in'),
                      ),
                    if (canRequestSwap) ...[
                      if (canCheckIn) const SizedBox(height: 8),
                      OutlinedButton.icon(
                        onPressed: hasRequestedSwap
                            ? () => _openSwapsTab(context)
                            : () => _requestSwap(context, ref, schedule),
                        icon: const Icon(Icons.swap_horiz),
                        label: Text(
                          hasRequestedSwap ? 'Swap Requested' : 'Request swap',
                        ),
                      ),
                    ],
                  ],
                ),
              ],
            ),
            if (relieverLabels.isNotEmpty)
              ExpansionTile(
                tilePadding: EdgeInsets.zero,
                title: const Text('Relievers'),
                children: [
                  for (final label in relieverLabels)
                    Align(
                      alignment: Alignment.centerLeft,
                      child: Padding(
                        padding: const EdgeInsets.only(
                          left: 8,
                          right: 8,
                          bottom: 6,
                        ),
                        child: Text(label),
                      ),
                    ),
                ],
              ),
          ],
        ),
      ),
    );
  }

  Future<void> _handleCheckIn(
    BuildContext context,
    WidgetRef ref,
    DutySchedule schedule,
  ) async {
    final geofence = await ref.read(geofenceProvider.future);
    if (geofence == null) {
      if (!context.mounted) return;
      await _showAlert(
        context,
        title: 'Geofence missing',
        message: 'Geofence is not configured.',
      );
      return;
    }

    final serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      if (!context.mounted) return;
      await _showAlert(
        context,
        title: 'Location disabled',
        message: 'Location services are disabled.',
      );
      return;
    }

    var permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
    }
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      if (!context.mounted) return;
      await _showAlert(
        context,
        title: 'Permission denied',
        message: 'Location permission denied.',
      );
      return;
    }
    if (!context.mounted) return;
    final progressContext = await _showCheckInProgress(context);
    try {
      final position = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high,
        ),
      );

      if (!geofence.hasPolygon) {
        if (!context.mounted) return;
        await _showAlert(
          context,
          title: 'Geofence missing',
          message: 'Geofence polygon is not configured.',
        );
        return;
      }

      final isInside = geofence.containsPolygon(
        position.latitude,
        position.longitude,
      );

      if (!isInside) {
        if (!context.mounted) return;
        await _showAlert(
          context,
          title: 'Outside geofence',
          message: 'You are outside the geofence. Wala ka sa CRMC.',
        );
        return;
      }

      final status = await ref
          .read(workforceControllerProvider)
          .checkIn(
            dutyScheduleId: schedule.id,
            lat: position.latitude,
            lng: position.longitude,
          );
      ref.invalidate(dutySchedulesProvider);
      if (!context.mounted) return;
      await _showAlert(
        context,
        title: 'Checked in',
        message: 'Checked in ($status).',
      );
    } catch (error) {
      if (!context.mounted) return;
      await _showAlert(
        context,
        title: 'Check-in failed',
        message: 'Check-in failed: $error',
      );
    } finally {
      if (progressContext.mounted) {
        Navigator.of(progressContext).pop();
      }
    }
  }

  Future<void> _requestSwap(
    BuildContext context,
    WidgetRef ref,
    DutySchedule schedule,
  ) async {
    final profiles = ref.read(profilesProvider).valueOrNull ?? [];
    final currentUserId = ref.read(currentUserIdProvider);
    final staff = profiles
        .where((profile) => profile.role == 'it_staff')
        .where((profile) => profile.id != currentUserId)
        .toList();
    if (staff.isEmpty) {
      _showMessage(context, 'No IT staff available for swaps.');
      return;
    }

    String? selectedId = staff.first.id;
    List<DutySchedule> recipientShifts = [];
    String? selectedTargetShiftId;

    final confirmed = await showDialog<bool>(
      context: context,
      builder: (dialogContext) {
        return StatefulBuilder(
          builder: (context, setState) {
            // initial load for the first recipient shown — only upcoming shifts
            if (recipientShifts.isEmpty && selectedId != null) {
              ref
                  .read(dutySchedulesForUserProvider(selectedId!).future)
                  .then((shifts) {
                    final now = AppTime.now();
                    final upcoming =
                        shifts.where((s) => !s.startTime.isBefore(now)).toList()
                          ..sort((a, b) => a.startTime.compareTo(b.startTime));
                    setState(() {
                      recipientShifts = upcoming;
                      selectedTargetShiftId = upcoming.isNotEmpty
                          ? upcoming.first.id
                          : null;
                    });
                  })
                  .catchError((_) {});
            }

            return AlertDialog(
              shape: AppSurfaces.of(context).dialogShape,
              title: const Text('Request swap'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  DropdownButtonFormField<String>(
                    initialValue: selectedId,
                    items: [
                      for (final profile in staff)
                        DropdownMenuItem(
                          value: profile.id,
                          child: Text(
                            profile.fullName.isNotEmpty
                                ? profile.fullName
                                : profile.id,
                          ),
                        ),
                    ],
                    onChanged: (value) async {
                      if (value == null) return;
                      setState(() => selectedId = value);
                      // load recipient shifts (only show upcoming)
                      final shifts = await ref
                          .read(dutySchedulesForUserProvider(value).future)
                          .catchError((_) => <DutySchedule>[]);
                      final now = AppTime.now();
                      final upcoming =
                          shifts
                              .where((s) => !s.startTime.isBefore(now))
                              .toList()
                            ..sort(
                              (a, b) => a.startTime.compareTo(b.startTime),
                            );
                      setState(() {
                        recipientShifts = upcoming;
                        selectedTargetShiftId = upcoming.isNotEmpty
                            ? upcoming.first.id
                            : null;
                      });
                    },
                    decoration: const InputDecoration(labelText: 'Recipient'),
                  ),
                  const SizedBox(height: 12),
                  DropdownButtonFormField<String>(
                    initialValue: selectedTargetShiftId,
                    items: [
                      for (final s in recipientShifts)
                        DropdownMenuItem(
                          value: s.id,
                          child: Text(
                            '${s.shiftType == 'am'
                                ? 'AM Duty'
                                : s.shiftType == 'pm'
                                ? 'PM Duty'
                                : s.shiftType} · ${AppTime.formatDate(s.startTime)} · ${AppTime.formatTime(s.startTime)}',
                          ),
                        ),
                    ],
                    onChanged: (value) =>
                        setState(() => selectedTargetShiftId = value),
                    decoration: const InputDecoration(
                      labelText: 'Recipient shift',
                    ),
                  ),
                ],
              ),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(dialogContext).pop(false),
                  child: const Text('Cancel'),
                ),
                FilledButton(
                  onPressed: () => Navigator.of(dialogContext).pop(true),
                  child: const Text('Send request'),
                ),
              ],
            );
          },
        );
      },
    );

    if (!context.mounted) {
      return;
    }
    if (confirmed != true ||
        selectedId == null ||
        selectedTargetShiftId == null) {
      return;
    }

    try {
      await ref
          .read(workforceControllerProvider)
          .requestSwap(
            requesterScheduleId: schedule.id,
            targetScheduleId: selectedTargetShiftId!,
            recipientId: selectedId!,
          );
      ref.invalidate(swapRequestsProvider);
      if (!context.mounted) return;
      _showMessage(context, 'Swap request sent.');
    } catch (error) {
      if (!context.mounted) return;
      _showMessage(context, 'Swap request failed: $error');
    }
  }

  void _showMessage(BuildContext context, String message) {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }

  Future<void> _showAlert(
    BuildContext context, {
    required String title,
    required String message,
  }) async {
    await showDialog<void>(
      context: context,
      builder: (dialogContext) {
        return AlertDialog(
          title: Text(title),
          content: Text(message),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(dialogContext).pop(),
              child: const Text('OK'),
            ),
          ],
        );
      },
    );
  }

  Future<BuildContext> _showCheckInProgress(BuildContext context) {
    final completer = Completer<BuildContext>();
    showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (dialogContext) {
        if (!completer.isCompleted) {
          completer.complete(dialogContext);
        }
        return AlertDialog(
          shape: AppSurfaces.of(context).dialogShape,
          title: const Text('Validating location'),
          content: Row(
            children: [
              const SizedBox(
                height: 24,
                width: 24,
                child: CircularProgressIndicator(strokeWidth: 2),
              ),
              const SizedBox(width: 12),
              Flexible(
                child: Text(
                  'Please wait while we verify your location.',
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
              ),
            ],
          ),
        );
      },
    );
    return completer.future;
  }

  void _openSwapsTab(BuildContext context) {
    final controller = DefaultTabController.maybeOf(context);
    if (controller != null) {
      controller.animateTo(1);
      return;
    }
    _showMessage(context, 'Swap request already sent. See Swaps panel.');
  }

  String _statusLabel(String status) {
    switch (status) {
      case 'arrival':
        return 'Arrival';
      case 'late':
        return 'Late';
      case 'absent':
        return 'Absent';
      default:
        return 'Scheduled';
    }
  }

  Color _statusColor(BuildContext context, String status) {
    switch (status) {
      case 'arrival':
        return Colors.green;
      case 'late':
        return Colors.orange;
      case 'absent':
        return Colors.red;
      default:
        return Theme.of(context).colorScheme.onSurfaceVariant;
    }
  }
}

class _DraftSchedule {
  _DraftSchedule({
    required this.localId,
    required this.userId,
    required this.shiftType,
    required this.startTime,
    required this.endTime,
    List<String>? relieverIds,
  }) : relieverIds = relieverIds ?? <String>[];

  final int localId;
  String userId;
  String shiftType;
  DateTime startTime;
  DateTime endTime;
  List<String> relieverIds;
}

class _RotationEntry {
  _RotationEntry({
    required this.userId,
    required this.shiftType,
    required this.startTime,
  });

  final String userId;
  final String shiftType;
  final DateTime startTime;
}

class _ShiftTemplate {
  _ShiftTemplate({
    required this.startHour,
    required this.startMinute,
    required this.duration,
  });

  final int startHour;
  final int startMinute;
  final Duration duration;

  DateTime buildStart(DateTime day) {
    return tz.TZDateTime(
      tz.local,
      day.year,
      day.month,
      day.day,
      startHour,
      startMinute,
    );
  }

  DateTime buildEnd(DateTime start) {
    return start.add(duration);
  }
}

class _ScheduleGeneratorPanel extends ConsumerStatefulWidget {
  const _ScheduleGeneratorPanel({required this.enabled});

  final bool enabled;

  @override
  ConsumerState<_ScheduleGeneratorPanel> createState() =>
      _ScheduleGeneratorPanelState();
}

class _ScheduleGeneratorPanelState
    extends ConsumerState<_ScheduleGeneratorPanel> {
  DateTime? _startDate;
  DateTime? _endDate;
  bool _isGenerating = false;
  bool _isSaving = false;
  int _draftCounter = 0;
  List<_DraftSchedule> _draftSchedules = [];
  List<String> _warnings = [];

  @override
  Widget build(BuildContext context) {
    if (!widget.enabled) {
      return const SizedBox.shrink();
    }

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Schedule Generator',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
            ),
            const SizedBox(height: 12),
            _dateField(
              context,
              label: 'Start date',
              value: _startDate,
              onTap: () => _pickDate(isStart: true),
            ),
            const SizedBox(height: 8),
            _dateField(
              context,
              label: 'End date',
              value: _endDate,
              onTap: () => _pickDate(isStart: false),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: FilledButton(
                    onPressed: _isGenerating ? null : _handleGenerate,
                    child: _isGenerating
                        ? const SizedBox(
                            height: 18,
                            width: 18,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Text('Generate preview'),
                  ),
                ),
                const SizedBox(width: 12),
                OutlinedButton(
                  onPressed: _draftSchedules.isEmpty ? null : _clearDraft,
                  child: const Text('Clear'),
                ),
              ],
            ),
            if (_warnings.isNotEmpty) ...[
              const SizedBox(height: 12),
              _buildWarningPanel(context),
            ],
            if (_draftSchedules.isNotEmpty) ...[
              const SizedBox(height: 12),
              _buildDraftHeader(context),
              const SizedBox(height: 8),
              Flexible(fit: FlexFit.loose, child: _buildDraftList(context)),
              const SizedBox(height: 12),
              Row(
                children: [
                  OutlinedButton.icon(
                    onPressed: _isSaving ? null : _addDraft,
                    icon: const Icon(Icons.add),
                    label: const Text('Add shift'),
                  ),
                  const Spacer(),
                  FilledButton.icon(
                    onPressed: _isSaving ? null : _commitDraft,
                    icon: const Icon(Icons.check_circle),
                    label: _isSaving
                        ? const SizedBox(
                            height: 18,
                            width: 18,
                            child: CircularProgressIndicator(strokeWidth: 2),
                          )
                        : const Text('Commit schedule'),
                  ),
                ],
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _dateField(
    BuildContext context, {
    required String label,
    required DateTime? value,
    required VoidCallback onTap,
  }) {
    return InkWell(
      onTap: onTap,
      child: InputDecorator(
        decoration: InputDecoration(labelText: label),
        child: Text(value == null ? 'Select date' : AppTime.formatDate(value)),
      ),
    );
  }

  Future<void> _pickDate({required bool isStart}) async {
    final now = AppTime.now();
    final initial = isStart ? _startDate ?? now : _endDate ?? _startDate ?? now;
    final picked = await showDatePicker(
      context: context,
      initialDate: initial,
      firstDate: DateTime(now.year - 1),
      lastDate: DateTime(now.year + 2),
    );
    if (picked == null) return;
    setState(() {
      if (isStart) {
        _startDate = picked;
      } else {
        _endDate = picked;
      }
    });
  }

  Future<void> _handleGenerate() async {
    final start = _startDate;
    final end = _endDate;
    if (start == null || end == null) {
      _showMessage('Select a date range.');
      return;
    }
    if (end.isBefore(start)) {
      _showMessage('End date must be after start date.');
      return;
    }
    setState(() => _isGenerating = true);
    try {
      final staff = _sortedStaff();
      if (staff.isEmpty) {
        if (!mounted) return;
        _showMessage('No IT staff available for scheduling.');
        return;
      }

      final schedules = ref.read(dutySchedulesProvider).valueOrNull ?? [];
      final templates = _buildTemplates(schedules);
      final generated = _generateDrafts(
        start,
        end,
        staff,
        schedules,
        templates,
      );
      generated.sort((a, b) => a.startTime.compareTo(b.startTime));
      final warnings = _buildWarnings(start, end, generated);

      if (!mounted) return;
      setState(() {
        _draftSchedules = generated;
        _warnings = warnings;
      });

      if (generated.isEmpty) {
        _showMessage('No shifts could be generated.');
      }
    } finally {
      if (mounted) {
        setState(() => _isGenerating = false);
      }
    }
  }

  void _clearDraft() {
    setState(() {
      _draftSchedules = [];
      _warnings = [];
    });
  }

  Widget _buildWarningPanel(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: colorScheme.tertiaryContainer,
        borderRadius: BorderRadius.circular(
          AppSurfaces.of(context).compactCardRadius,
        ),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Icon(Icons.warning_amber, color: colorScheme.onTertiaryContainer),
          const SizedBox(width: 8),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Uncovered shifts',
                  style: Theme.of(context).textTheme.titleSmall?.copyWith(
                    fontWeight: FontWeight.w700,
                    color: colorScheme.onTertiaryContainer,
                  ),
                ),
                const SizedBox(height: 6),
                for (final warning in _warnings)
                  Text(
                    warning,
                    style: Theme.of(context).textTheme.bodySmall?.copyWith(
                      color: colorScheme.onTertiaryContainer,
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDraftHeader(BuildContext context) {
    final start = _startDate == null ? '' : AppTime.formatDate(_startDate!);
    final end = _endDate == null ? '' : AppTime.formatDate(_endDate!);
    return Row(
      children: [
        Text(
          'Preview (${_draftSchedules.length})',
          style: Theme.of(
            context,
          ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700),
        ),
        const Spacer(),
        if (start.isNotEmpty && end.isNotEmpty)
          Text('$start → $end', style: Theme.of(context).textTheme.bodySmall),
      ],
    );
  }

  Widget _buildDraftList(BuildContext context) {
    return ListView.separated(
      primary: false,
      itemCount: _draftSchedules.length,
      separatorBuilder: (context, index) => const SizedBox(height: 8),
      itemBuilder: (context, index) {
        final draft = _draftSchedules[index];
        final profileById = _profileById();
        final profile = profileById[draft.userId];
        final userLabel = profile?.fullName.isNotEmpty == true
            ? profile!.fullName
            : draft.userId;
        final relieverLabels = draft.relieverIds
            .map(
              (id) => profileById[id]?.fullName.isNotEmpty == true
                  ? profileById[id]!.fullName
                  : id,
            )
            .toList();
        return Card(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
            child: Column(
              children: [
                Row(
                  children: [
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            '${_shiftLabel(draft.shiftType)} · $userLabel',
                            style: Theme.of(context).textTheme.bodyMedium
                                ?.copyWith(fontWeight: FontWeight.w600),
                          ),
                          const SizedBox(height: 4),
                          Text(
                            '${AppTime.formatDate(draft.startTime)} · ${AppTime.formatTime(draft.startTime)} - ${AppTime.formatTime(draft.endTime)}',
                            style: Theme.of(context).textTheme.bodySmall,
                          ),
                        ],
                      ),
                    ),
                    IconButton(
                      tooltip: 'Edit',
                      onPressed: () => _editDraft(draft),
                      icon: const Icon(Icons.edit),
                    ),
                    IconButton(
                      tooltip: 'Delete',
                      onPressed: () => _deleteDraft(draft),
                      icon: const Icon(Icons.delete_outline),
                    ),
                  ],
                ),
                if (relieverLabels.isNotEmpty)
                  ExpansionTile(
                    tilePadding: EdgeInsets.zero,
                    title: const Text('Relievers'),
                    children: [
                      for (final label in relieverLabels)
                        Align(
                          alignment: Alignment.centerLeft,
                          child: Padding(
                            padding: const EdgeInsets.only(
                              left: 8,
                              right: 8,
                              bottom: 6,
                            ),
                            child: Text(label),
                          ),
                        ),
                    ],
                  ),
              ],
            ),
          ),
        );
      },
    );
  }

  void _addDraft() {
    _openDraftEditor();
  }

  void _editDraft(_DraftSchedule draft) {
    _openDraftEditor(existing: draft);
  }

  void _deleteDraft(_DraftSchedule draft) {
    setState(() {
      _draftSchedules.removeWhere((item) => item.localId == draft.localId);
      _warnings = _buildWarnings(
        _startDate ?? AppTime.now(),
        _endDate ?? AppTime.now(),
        _draftSchedules,
      );
    });
  }

  Future<void> _openDraftEditor({_DraftSchedule? existing}) async {
    final staff = _sortedStaff();
    if (staff.isEmpty) {
      _showMessage('No IT staff available.');
      return;
    }

    final start = existing?.startTime ?? _startDate ?? AppTime.now();
    var selectedDate = DateTime(start.year, start.month, start.day);
    var selectedUserId = existing?.userId ?? staff.first.id;
    var selectedShift = existing?.shiftType ?? 'am';
    var startTime = TimeOfDay.fromDateTime(existing?.startTime ?? start);
    var endTime = TimeOfDay.fromDateTime(
      existing?.endTime ?? start.add(const Duration(hours: 8)),
    );

    final result = await showDialog<_DraftSchedule>(
      context: context,
      builder: (dialogContext) {
        return StatefulBuilder(
          builder: (context, setDialogState) {
            return AlertDialog(
              title: Text(existing == null ? 'Add shift' : 'Edit shift'),
              content: SingleChildScrollView(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    DropdownButtonFormField<String>(
                      initialValue: selectedUserId,
                      items: [
                        for (final profile in staff)
                          DropdownMenuItem(
                            value: profile.id,
                            child: Text(
                              profile.fullName.isNotEmpty
                                  ? profile.fullName
                                  : profile.id,
                            ),
                          ),
                      ],
                      onChanged: (value) {
                        if (value == null) return;
                        setDialogState(() => selectedUserId = value);
                      },
                      decoration: const InputDecoration(labelText: 'Assignee'),
                    ),
                    const SizedBox(height: 12),
                    DropdownButtonFormField<String>(
                      initialValue: selectedShift,
                      items: const [
                        DropdownMenuItem(value: 'am', child: Text('AM Duty')),
                        DropdownMenuItem(
                          value: 'on_call',
                          child: Text('On Call'),
                        ),
                        DropdownMenuItem(value: 'pm', child: Text('PM Duty')),
                      ],
                      onChanged: (value) {
                        if (value == null) return;
                        setDialogState(() => selectedShift = value);
                      },
                      decoration: const InputDecoration(
                        labelText: 'Shift type',
                      ),
                    ),
                    const SizedBox(height: 12),
                    _dialogDateField(
                      label: 'Date',
                      value: AppTime.formatDate(selectedDate),
                      onTap: () async {
                        final picked = await showDatePicker(
                          context: context,
                          initialDate: selectedDate,
                          firstDate: DateTime(2020),
                          lastDate: DateTime(2100),
                        );
                        if (picked == null) return;
                        setDialogState(() {
                          selectedDate = picked;
                        });
                      },
                    ),
                    const SizedBox(height: 12),
                    _dialogTimeField(
                      label: 'Start time',
                      value: startTime,
                      onTap: () async {
                        final picked = await showTimePicker(
                          context: context,
                          initialTime: startTime,
                        );
                        if (picked == null) return;
                        setDialogState(() => startTime = picked);
                      },
                    ),
                    const SizedBox(height: 12),
                    _dialogTimeField(
                      label: 'End time',
                      value: endTime,
                      onTap: () async {
                        final picked = await showTimePicker(
                          context: context,
                          initialTime: endTime,
                        );
                        if (picked == null) return;
                        setDialogState(() => endTime = picked);
                      },
                    ),
                  ],
                ),
              ),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(dialogContext).pop(),
                  child: const Text('Cancel'),
                ),
                FilledButton(
                  onPressed: () {
                    final startDateTime = DateTime(
                      selectedDate.year,
                      selectedDate.month,
                      selectedDate.day,
                      startTime.hour,
                      startTime.minute,
                    );
                    var endDateTime = DateTime(
                      selectedDate.year,
                      selectedDate.month,
                      selectedDate.day,
                      endTime.hour,
                      endTime.minute,
                    );
                    if (!endDateTime.isAfter(startDateTime)) {
                      endDateTime = endDateTime.add(const Duration(days: 1));
                    }

                    final draft =
                        existing ??
                        _DraftSchedule(
                          localId: _draftCounter++,
                          userId: selectedUserId,
                          shiftType: selectedShift,
                          startTime: startDateTime,
                          endTime: endDateTime,
                        );

                    draft
                      ..userId = selectedUserId
                      ..shiftType = selectedShift
                      ..startTime = startDateTime
                      ..endTime = endDateTime;

                    Navigator.of(dialogContext).pop(draft);
                  },
                  child: const Text('Save'),
                ),
              ],
            );
          },
        );
      },
    );

    if (!mounted || result == null) return;
    setState(() {
      if (existing == null) {
        _draftSchedules.add(result);
      }
      _draftSchedules.sort((a, b) => a.startTime.compareTo(b.startTime));
      _warnings = _buildWarnings(
        _startDate ?? result.startTime,
        _endDate ?? result.endTime,
        _draftSchedules,
      );
    });
  }

  Widget _dialogDateField({
    required String label,
    required String value,
    required VoidCallback onTap,
  }) {
    return InkWell(
      onTap: onTap,
      child: InputDecorator(
        decoration: InputDecoration(labelText: label),
        child: Text(value),
      ),
    );
  }

  Widget _dialogTimeField({
    required String label,
    required TimeOfDay value,
    required VoidCallback onTap,
  }) {
    return InkWell(
      onTap: onTap,
      child: InputDecorator(
        decoration: InputDecoration(labelText: label),
        child: Text(value.format(context)),
      ),
    );
  }

  Future<void> _commitDraft() async {
    final start = _startDate;
    final end = _endDate;
    if (start == null || end == null) {
      _showMessage('Select a date range.');
      return;
    }

    final schedules = ref.read(dutySchedulesProvider).valueOrNull ?? [];
    final conflict = _findConflict(_draftSchedules, schedules);
    if (conflict != null) {
      _showMessage(conflict);
      return;
    }

    final payload = _draftSchedules
        .map(
          (draft) => {
            'user_id': draft.userId,
            'shift_type': draft.shiftType,
            'start_time': draft.startTime.toIso8601String(),
            'end_time': draft.endTime.toIso8601String(),
            'status': 'scheduled',
            'reliever_ids': draft.relieverIds,
          },
        )
        .toList();

    setState(() => _isSaving = true);
    try {
      await ref.read(workforceControllerProvider).insertSchedules(payload);
      ref.invalidate(dutySchedulesProvider);
      if (!mounted) return;
      _showMessage('Schedule committed.');
      setState(() {
        _draftSchedules = [];
        _warnings = [];
      });
    } catch (error) {
      if (!mounted) return;
      _showMessage('Commit failed: $error');
    } finally {
      if (mounted) {
        setState(() => _isSaving = false);
      }
    }
  }

  List<Profile> _sortedStaff() {
    final profiles = ref.read(profilesProvider).valueOrNull ?? [];
    final staff = profiles
        .where((profile) => profile.role == 'it_staff')
        .toList();
    staff.sort((a, b) {
      final nameA = a.fullName.isNotEmpty ? a.fullName : a.id;
      final nameB = b.fullName.isNotEmpty ? b.fullName : b.id;
      final result = nameA.compareTo(nameB);
      if (result != 0) return result;
      return a.id.compareTo(b.id);
    });
    return staff;
  }

  Map<String, Profile> _profileById() {
    final profiles = ref.read(profilesProvider).valueOrNull ?? [];
    return {for (final profile in profiles) profile.id: profile};
  }

  Map<String, _ShiftTemplate> _buildTemplates(List<DutySchedule> schedules) {
    final templates = <String, _ShiftTemplate>{};
    for (final schedule in schedules) {
      final key = _normalizeShiftType(schedule.shiftType);
      if (templates.containsKey(key)) continue;
      final start = schedule.startTime;
      var end = schedule.endTime;
      if (!end.isAfter(start)) {
        end = end.add(const Duration(days: 1));
      }
      templates[key] = _ShiftTemplate(
        startHour: start.hour,
        startMinute: start.minute,
        duration: end.difference(start),
      );
    }
    templates['am'] = _ShiftTemplate(
      startHour: 7,
      startMinute: 0,
      duration: const Duration(hours: 8),
    );
    templates['pm'] = _ShiftTemplate(
      startHour: 15,
      startMinute: 0,
      duration: const Duration(hours: 8),
    );
    templates['on_call'] = _ShiftTemplate(
      startHour: 23,
      startMinute: 0,
      duration: const Duration(hours: 8),
    );
    templates['normal'] = _ShiftTemplate(
      startHour: 8,
      startMinute: 0,
      duration: const Duration(hours: 9),
    );
    return templates;
  }

  List<_DraftSchedule> _generateDrafts(
    DateTime start,
    DateTime end,
    List<Profile> staff,
    List<DutySchedule> schedules,
    Map<String, _ShiftTemplate> templates,
  ) {
    final draft = <_DraftSchedule>[];
    final normalizedStart = DateTime(start.year, start.month, start.day);
    final normalizedEnd = DateTime(end.year, end.month, end.day);
    final existing = schedules;
    var weekStart = _startOfWeek(normalizedStart);

    while (!weekStart.isAfter(normalizedEnd)) {
      final weekEnd = weekStart.add(const Duration(days: 6));
      final prevWeekStart = weekStart.subtract(const Duration(days: 7));
      final prevWeekEnd = weekStart.subtract(const Duration(days: 1));
      final lastWeek = <_RotationEntry>[
        ...existing
            .where(
              (schedule) =>
                  !schedule.startTime.isBefore(prevWeekStart) &&
                  !schedule.startTime.isAfter(prevWeekEnd),
            )
            .map(
              (schedule) => _RotationEntry(
                userId: schedule.userId,
                shiftType: schedule.shiftType,
                startTime: schedule.startTime,
              ),
            ),
        ...draft
            .where(
              (item) =>
                  !item.startTime.isBefore(prevWeekStart) &&
                  !item.startTime.isAfter(prevWeekEnd),
            )
            .map(
              (item) => _RotationEntry(
                userId: item.userId,
                shiftType: item.shiftType,
                startTime: item.startTime,
              ),
            ),
      ];

      final amBaseIndex = _nextIndexFromLastWeek(
        shiftType: 'am',
        staff: staff,
        lastWeek: lastWeek,
        defaultIndex: 0,
      );
      final pmBaseIndex = _nextIndexFromLastWeek(
        shiftType: 'pm',
        staff: staff,
        lastWeek: lastWeek,
        defaultIndex: staff.length > 1 ? 1 : 0,
      );
      final amUserId = staff.isEmpty
          ? null
          : staff[amBaseIndex % staff.length].id;
      final pmUserId = staff.isEmpty
          ? null
          : staff[pmBaseIndex % staff.length].id;
      final nextWeekPmUserId = staff.isEmpty
          ? null
          : staff[(pmBaseIndex + 1) % staff.length].id;
      final pmRelievers = _buildRelievers(pmBaseIndex, staff);
      final nextWeekRelievers = staff.isEmpty
          ? <String>[]
          : _buildRelievers((pmBaseIndex + 1) % staff.length, staff);
      var weekendNormalOffset = 0;

      for (
        var day = weekStart;
        !day.isAfter(weekEnd);
        day = day.add(const Duration(days: 1))
      ) {
        if (day.isBefore(normalizedStart) || day.isAfter(normalizedEnd)) {
          continue;
        }
        final isWeekend =
            day.weekday == DateTime.saturday || day.weekday == DateTime.sunday;
        if (isWeekend) {
          if (staff.isNotEmpty) {
            final normalIndex =
                (amBaseIndex + pmBaseIndex + weekendNormalOffset) %
                staff.length;
            _tryAddDraft(
              draft,
              existing,
              templates,
              'normal',
              staff[normalIndex].id,
              day,
              const [],
            );
            weekendNormalOffset += 1;
          }
          if (nextWeekPmUserId != null) {
            _tryAddDraft(
              draft,
              existing,
              templates,
              'on_call',
              nextWeekPmUserId,
              day,
              nextWeekRelievers,
            );
          }
        } else {
          if (amUserId != null) {
            _tryAddDraft(
              draft,
              existing,
              templates,
              'am',
              amUserId,
              day,
              const [],
            );
          }
          if (pmUserId != null) {
            _tryAddDraft(
              draft,
              existing,
              templates,
              'pm',
              pmUserId,
              day,
              pmRelievers,
            );
            _tryAddDraft(
              draft,
              existing,
              templates,
              'on_call',
              pmUserId,
              day,
              pmRelievers,
            );
          }

          final assignedToday = <String?>[
            amUserId,
            pmUserId,
          ].whereType<String>().toSet();
          for (final profile in staff) {
            if (assignedToday.contains(profile.id)) continue;
            _tryAddDraft(
              draft,
              existing,
              templates,
              'normal',
              profile.id,
              day,
              const [],
            );
          }
        }
      }

      weekStart = weekStart.add(const Duration(days: 7));
    }

    return draft;
  }

  void _tryAddDraft(
    List<_DraftSchedule> draft,
    List<DutySchedule> existing,
    Map<String, _ShiftTemplate> templates,
    String shiftType,
    String userId,
    DateTime day,
    List<String> relieverIds,
  ) {
    final template = templates[_normalizeShiftType(shiftType)]!;
    final start = template.buildStart(day);
    final end = template.buildEnd(start);
    final candidate = _DraftSchedule(
      localId: _draftCounter++,
      userId: userId,
      shiftType: shiftType,
      startTime: start,
      endTime: end,
      relieverIds: relieverIds,
    );

    if (_hasConflict(candidate, draft, existing)) {
      return;
    }
    draft.add(candidate);
  }

  int _nextIndexFromLastWeek({
    required String shiftType,
    required List<Profile> staff,
    required Iterable<_RotationEntry> lastWeek,
    required int defaultIndex,
  }) {
    if (staff.isEmpty) return 0;
    final normalized = _normalizeShiftType(shiftType);
    final lastAssignment =
        lastWeek
            .where(
              (entry) => _normalizeShiftType(entry.shiftType) == normalized,
            )
            .toList()
          ..sort((a, b) => a.startTime.compareTo(b.startTime));

    if (lastAssignment.isNotEmpty) {
      final lastUserId = lastAssignment.last.userId;
      final index = staff.indexWhere((profile) => profile.id == lastUserId);
      if (index != -1) {
        return (index + 1) % staff.length;
      }
    }

    return defaultIndex % staff.length;
  }

  List<String> _buildRelievers(int primaryIndex, List<Profile> staff) {
    if (staff.length <= 1) return const [];
    final relievers = <String>[];
    for (var offset = 1; offset < staff.length; offset += 1) {
      relievers.add(staff[(primaryIndex + offset) % staff.length].id);
      if (relievers.length == 3) break;
    }
    return relievers;
  }

  bool _hasConflict(
    _DraftSchedule candidate,
    List<_DraftSchedule> drafts,
    List<DutySchedule> existing,
  ) {
    for (final draft in drafts) {
      if (draft.userId != candidate.userId) continue;
      if (_overlaps(
        candidate.startTime,
        candidate.endTime,
        draft.startTime,
        draft.endTime,
      )) {
        return true;
      }
    }
    for (final schedule in existing) {
      if (schedule.userId != candidate.userId) continue;
      if (_overlaps(
        candidate.startTime,
        candidate.endTime,
        schedule.startTime,
        schedule.endTime,
      )) {
        return true;
      }
    }
    return false;
  }

  String? _findConflict(
    List<_DraftSchedule> drafts,
    List<DutySchedule> existing,
  ) {
    for (final draft in drafts) {
      if (_hasConflict(
        draft,
        drafts.where((d) => d.localId != draft.localId).toList(),
        existing,
      )) {
        return 'Conflict found for ${AppTime.formatDate(draft.startTime)}.';
      }
    }
    return null;
  }

  bool _overlaps(
    DateTime startA,
    DateTime endA,
    DateTime startB,
    DateTime endB,
  ) {
    return startA.isBefore(endB) && endA.isAfter(startB);
  }

  List<String> _buildWarnings(
    DateTime start,
    DateTime end,
    List<_DraftSchedule> drafts,
  ) {
    final warnings = <String>[];
    var day = DateTime(start.year, start.month, start.day);
    final lastDay = DateTime(end.year, end.month, end.day);

    while (!day.isAfter(lastDay)) {
      final isWeekend =
          day.weekday == DateTime.saturday || day.weekday == DateTime.sunday;
      final required = <String>{'on_call'};
      if (isWeekend) {
        required.add('normal');
      } else {
        required.addAll({'am', 'pm'});
      }

      final available = drafts
          .where(
            (draft) =>
                draft.startTime.year == day.year &&
                draft.startTime.month == day.month &&
                draft.startTime.day == day.day,
          )
          .map((draft) => _normalizeShiftType(draft.shiftType))
          .toSet();

      for (final shift in required) {
        if (!available.contains(shift)) {
          warnings.add(
            '${AppTime.formatDate(day)} missing ${_shiftLabel(shift)}',
          );
        }
      }

      day = day.add(const Duration(days: 1));
    }

    return warnings;
  }

  DateTime _startOfWeek(DateTime day) {
    return day.subtract(Duration(days: day.weekday - 1));
  }

  String _normalizeShiftType(String value) {
    return value;
  }

  String _shiftLabel(String value) {
    switch (value) {
      case 'am':
        return 'AM Duty';
      case 'pm':
        return 'PM Duty';
      case 'on_call':
        return 'On Call';
      case 'normal':
        return 'Normal';
      case 'weekend':
        return 'Weekend';
      default:
        return value;
    }
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }
}

class _SwapRequestsPanel extends ConsumerWidget {
  const _SwapRequestsPanel({required this.isAdmin});

  final bool isAdmin;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final swapsAsync = ref.watch(swapRequestsProvider);
    final schedulesAsync = ref.watch(dutySchedulesProvider);
    final profilesAsync = ref.watch(profilesProvider);
    final currentUserId = ref.watch(currentUserIdProvider);

    final Map<String, DutySchedule> scheduleById = {
      for (final schedule in schedulesAsync.valueOrNull ?? [])
        schedule.id: schedule,
    };
    final Map<String, Profile> profileById = {
      for (final profile in profilesAsync.valueOrNull ?? [])
        profile.id: profile,
    };

    return swapsAsync.when(
      data: (items) {
        if (items.isEmpty) {
          return const Center(child: Text('No swap requests.'));
        }

        // If a swap references schedules that aren't in the current
        // `dutySchedulesProvider` (for example the requester owns the shift),
        // fetch those schedules by id so we can render shift details instead of
        // "Shift not found".
        final missingIds = items
            .expand(
              (s) => [
                s.requesterScheduleId,
                if (s.targetScheduleId != null) s.targetScheduleId!,
              ],
            )
            .where((id) => !scheduleById.containsKey(id))
            .toSet()
            .toList();
        final missingSchedules =
            ref.watch(dutySchedulesByIdsProvider(missingIds)).valueOrNull ?? [];
        for (final s in missingSchedules) {
          scheduleById[s.id] = s;
        }

        return ListView.separated(
          padding: const EdgeInsets.only(bottom: 24),
          itemCount: items.length,
          separatorBuilder: (context, index) => const SizedBox(height: 12),
          itemBuilder: (context, index) {
            final item = items[index];
            final requesterSchedule = scheduleById[item.requesterScheduleId];
            final targetSchedule = item.targetScheduleId != null
                ? scheduleById[item.targetScheduleId!]
                : null;

            final requesterProfile = profileById[item.requesterId];
            final recipientProfile = profileById[item.recipientId];
            final requester = requesterProfile?.fullName.isNotEmpty == true
                ? requesterProfile!.fullName
                : item.requesterId;
            final recipient = recipientProfile?.fullName.isNotEmpty == true
                ? recipientProfile!.fullName
                : item.recipientId;

            String subtitle;
            if (requesterSchedule != null && targetSchedule != null) {
              subtitle =
                  '${_shiftLabel(requesterSchedule.shiftType)} · ${AppTime.formatDate(requesterSchedule.startTime)} · ${AppTime.formatTime(requesterSchedule.startTime)} → ${_shiftLabel(targetSchedule.shiftType)} · ${AppTime.formatDate(targetSchedule.startTime)} · ${AppTime.formatTime(targetSchedule.startTime)}';
            } else if (requesterSchedule != null) {
              subtitle =
                  '${_shiftLabel(requesterSchedule.shiftType)} · ${AppTime.formatDate(requesterSchedule.startTime)} · ${AppTime.formatTime(requesterSchedule.startTime)}';
            } else if (item.shiftStartTime != null) {
              subtitle =
                  '${_shiftLabel(item.shiftType ?? 'normal')} · ${AppTime.formatDate(item.shiftStartTime!)} · ${AppTime.formatTime(item.shiftStartTime!)}';
            } else {
              subtitle = 'Shift not found';
            }

            final relieverLabels = requesterSchedule != null
                ? _relieverLabelsFromIds(
                    requesterSchedule.relieverIds,
                    profileById,
                  )
                : (item.relieverIds?.isNotEmpty == true
                      ? item.relieverIds!
                            .map((id) => profileById[id]?.fullName ?? id)
                            .toList()
                      : const <String>[]);

            final isPending = item.status == 'pending';
            // Admins may act on regular pending swaps and also on escalated
            // swaps (status == 'admin_review'). Standard recipients can only
            // act when the swap is pending.
            final canRespond =
                (isPending && (isAdmin || item.recipientId == currentUserId)) ||
                (isAdmin && item.status == 'admin_review');
            final canEscalate = item.requesterId == currentUserId && isPending;

            return Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '$requester → $recipient',
                      style: Theme.of(context).textTheme.titleSmall?.copyWith(
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                    const SizedBox(height: 6),
                    Text(subtitle),
                    const SizedBox(height: 6),
                    Text('Status: ${item.status}'),
                    const SizedBox(height: 12),
                    if (relieverLabels.isNotEmpty)
                      ExpansionTile(
                        tilePadding: EdgeInsets.zero,
                        title: const Text('Relievers'),
                        children: [
                          for (final label in relieverLabels)
                            Align(
                              alignment: Alignment.centerLeft,
                              child: Padding(
                                padding: const EdgeInsets.only(
                                  left: 8,
                                  right: 8,
                                  bottom: 6,
                                ),
                                child: Text(label),
                              ),
                            ),
                        ],
                      ),
                    Row(
                      children: [
                        if (canRespond) ...[
                          OutlinedButton(
                            onPressed: () => _respond(ref, item, 'accepted'),
                            child: const Text('Accept'),
                          ),
                          const SizedBox(width: 8),
                          OutlinedButton(
                            onPressed: () => _respond(ref, item, 'rejected'),
                            child: const Text('Reject'),
                          ),
                        ],
                        if (isAdmin && item.status == 'admin_review') ...[
                          const SizedBox(width: 8),
                          OutlinedButton(
                            onPressed: () =>
                                _changeRecipient(context, ref, item),
                            child: const Text('Change recipient'),
                          ),
                        ],
                        if (canEscalate) ...[
                          const SizedBox(width: 8),
                          OutlinedButton(
                            onPressed: () =>
                                _respond(ref, item, 'admin_review'),
                            child: const Text('Escalate'),
                          ),
                        ],
                      ],
                    ),
                  ],
                ),
              ),
            );
          },
        );
      },
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, _) => Center(child: Text('Failed to load swaps: $error')),
    );
  }

  Future<void> _respond(
    WidgetRef ref,
    SwapRequest request,
    String action,
  ) async {
    await ref
        .read(workforceControllerProvider)
        .respondSwap(swapId: request.id, action: action);
    ref.invalidate(swapRequestsProvider);
  }

  Future<void> _changeRecipient(
    BuildContext context,
    WidgetRef ref,
    SwapRequest request,
  ) async {
    final profiles = ref.watch(profilesProvider).valueOrNull ?? [];

    final eligible = profiles
        .where(
          (p) => p.id != request.requesterId && p.id != request.recipientId,
        )
        .toList();

    if (eligible.isEmpty) {
      // nothing to choose from
      return;
    }

    Profile? choice = eligible.first;
    final selected = await showDialog<Profile?>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Change recipient'),
          content: StatefulBuilder(
            builder: (context, setState) => DropdownButtonFormField<Profile>(
              initialValue: choice,
              items: eligible
                  .map(
                    (p) => DropdownMenuItem(value: p, child: Text(p.fullName)),
                  )
                  .toList(),
              onChanged: (v) => setState(() => choice = v),
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(null),
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(choice),
              child: const Text('Save'),
            ),
          ],
        );
      },
    );

    if (selected == null) return;

    await ref
        .read(workforceControllerProvider)
        .reassignSwap(swapId: request.id, newRecipientId: selected.id);
    ref.invalidate(swapRequestsProvider);
  }

  String _shiftLabel(String value) {
    switch (value) {
      case 'am':
        return 'AM Duty';
      case 'pm':
        return 'PM Duty';
      case 'on_call':
        return 'On Call';
      case 'normal':
        return 'Normal';
      case 'weekend':
        return 'Weekend';
      default:
        return value;
    }
  }

  List<String> _relieverLabelsFromIds(
    List<String> relieverIds,
    Map<String, Profile> profileById,
  ) {
    if (relieverIds.isEmpty) return const [];
    return relieverIds
        .map(
          (id) => profileById[id]?.fullName.isNotEmpty == true
              ? profileById[id]!.fullName
              : id,
        )
        .toList();
  }
}
