import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';

import 'package:household_keeper/features/home/presentation/calendar_providers.dart';

/// Number of days in the past and future to show in the strip.
const _kPastDays = 90;
const _kFutureDays = 90;

/// Total number of day cards in the strip.
const _kTotalDays = _kPastDays + 1 + _kFutureDays;

/// Fixed card width and height for each day card.
const _kCardWidth = 56.0;
const _kCardHeight = 72.0;

/// Default horizontal margin between cards.
const _kCardMargin = 4.0;

/// Wider gap inserted at month boundaries (left side margin of the first-of-month card).
const _kMonthBoundaryGap = 16.0;

/// Controller that allows external code (e.g. the Today button) to trigger
/// a scroll-to-today animation on the strip.
class CalendarStripController {
  VoidCallback? _scrollToToday;

  /// Animate the strip to center today's card.
  void scrollToToday() => _scrollToToday?.call();
}

/// A horizontal scrollable strip of day cards spanning [_kPastDays] days in the
/// past and [_kFutureDays] days in the future.
///
/// Each card shows:
/// - German day abbreviation (Mo, Di, Mi, Do, Fr, Sa, So)
/// - Date number (day of month)
///
/// The selected card is highlighted and always centered.
/// Today's card uses bold text + an accent underline bar.
/// Month boundaries get a wider gap and a small month label.
class CalendarStrip extends ConsumerStatefulWidget {
  const CalendarStrip({
    super.key,
    required this.controller,
    required this.onTodayVisibilityChanged,
  });

  /// Controller for programmatic scroll-to-today.
  final CalendarStripController controller;

  /// Called when today's card enters or leaves the viewport.
  final ValueChanged<bool> onTodayVisibilityChanged;

  @override
  ConsumerState<CalendarStrip> createState() => _CalendarStripState();
}

class _CalendarStripState extends ConsumerState<CalendarStrip> {
  late final ScrollController _scrollController;
  late final DateTime _today;
  late final List<DateTime> _dates;

  @override
  void initState() {
    super.initState();

    final now = DateTime.now();
    _today = DateTime(now.year, now.month, now.day);

    // Build the date list: _kPastDays before today, today, _kFutureDays after.
    _dates = List.generate(
      _kTotalDays,
      (i) => _today.subtract(Duration(days: _kPastDays - i)),
    );

    // Calculate initial scroll offset so today's card is centered.
    _scrollController = ScrollController(
      initialScrollOffset: _offsetForIndex(_kPastDays),
    );

    _scrollController.addListener(_onScroll);

    // Register the scroll-to-today callback on the controller.
    widget.controller._scrollToToday = _animateToToday;

    // After first frame, animate to center today with a short delay so the
    // strip has laid out its children.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        _animateToToday();
        // Initial visibility check
        _onScroll();
      }
    });
  }

  @override
  void dispose() {
    _scrollController.removeListener(_onScroll);
    _scrollController.dispose();
    super.dispose();
  }

  /// Returns the scroll offset that centers the card at [index].
  double _offsetForIndex(int index) {
    // Sum the widths of all items before [index], then subtract half the viewport
    // width so the card is centered.  We approximate viewport as screen width
    // because we cannot access it here; we compensate in the post-frame callback.
    double offset = 0;
    for (int i = 0; i < index; i++) {
      offset += _itemWidth(i);
    }
    // Center by subtracting half the card container width (will be corrected post-frame).
    return offset;
  }

  /// Returns the total width occupied by the item at [index], including margins
  /// and any month-boundary gap on its left side.
  double _itemWidth(int index) {
    final date = _dates[index];
    final leftMargin = _isFirstOfMonth(date) && index > 0
        ? _kMonthBoundaryGap
        : _kCardMargin;
    // Each item = leftMargin + card width + rightMargin
    return leftMargin + _kCardWidth + _kCardMargin;
  }

  bool _isFirstOfMonth(DateTime date) => date.day == 1;

  void _animateToToday() {
    if (!mounted || !_scrollController.hasClients) return;
    final viewportWidth = _scrollController.position.viewportDimension;
    double targetOffset = 0;
    for (int i = 0; i < _kPastDays; i++) {
      targetOffset += _itemWidth(i);
    }
    // Center today's card in the viewport.
    targetOffset -= (viewportWidth - _kCardWidth) / 2;
    targetOffset = targetOffset.clamp(
      _scrollController.position.minScrollExtent,
      _scrollController.position.maxScrollExtent,
    );
    _scrollController.animateTo(
      targetOffset,
      duration: const Duration(milliseconds: 200),
      curve: Curves.easeOut,
    );
  }

  void _animateToIndex(int index) {
    if (!mounted || !_scrollController.hasClients) return;
    final viewportWidth = _scrollController.position.viewportDimension;
    double targetOffset = 0;
    for (int i = 0; i < index; i++) {
      targetOffset += _itemWidth(i);
    }
    targetOffset -= (viewportWidth - _kCardWidth) / 2;
    targetOffset = targetOffset.clamp(
      _scrollController.position.minScrollExtent,
      _scrollController.position.maxScrollExtent,
    );
    _scrollController.animateTo(
      targetOffset,
      duration: const Duration(milliseconds: 250),
      curve: Curves.easeOut,
    );
  }

  void _onScroll() {
    if (!mounted || !_scrollController.hasClients) return;
    final viewportWidth = _scrollController.position.viewportDimension;
    final scrollOffset = _scrollController.offset;

    // Calculate the left edge of today's card.
    double todayLeftEdge = 0;
    for (int i = 0; i < _kPastDays; i++) {
      todayLeftEdge += _itemWidth(i);
    }
    final todayRightEdge = todayLeftEdge + _kCardWidth;

    // Today is visible if any part of the card is in the viewport.
    final isVisible =
        todayRightEdge > scrollOffset &&
        todayLeftEdge < scrollOffset + viewportWidth;

    widget.onTodayVisibilityChanged(isVisible);
  }

  void _onCardTapped(int index) {
    final tappedDate = _dates[index];
    ref.read(selectedDateProvider.notifier).selectDate(tappedDate);
    _animateToIndex(index);
  }

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

    return SizedBox(
      height: _kCardHeight + 24, // extra height for month label
      child: ListView.builder(
        controller: _scrollController,
        scrollDirection: Axis.horizontal,
        itemCount: _kTotalDays,
        itemBuilder: (context, index) {
          final date = _dates[index];
          final isToday = date == _today;
          final isSelected = date == selectedDate;
          final isFirstOfMonth = _isFirstOfMonth(date) && index > 0;

          return _DayCardItem(
            date: date,
            isToday: isToday,
            isSelected: isSelected,
            isFirstOfMonth: isFirstOfMonth,
            onTap: () => _onCardTapped(index),
            theme: theme,
          );
        },
      ),
    );
  }
}

/// A single day card in the calendar strip, with optional month boundary label.
class _DayCardItem extends StatelessWidget {
  const _DayCardItem({
    required this.date,
    required this.isToday,
    required this.isSelected,
    required this.isFirstOfMonth,
    required this.onTap,
    required this.theme,
  });

  final DateTime date;
  final bool isToday;
  final bool isSelected;
  final bool isFirstOfMonth;
  final VoidCallback onTap;
  final ThemeData theme;

  @override
  Widget build(BuildContext context) {
    final leftMargin = isFirstOfMonth ? _kMonthBoundaryGap : _kCardMargin;

    // Card background color: selected gets full primaryContainer, others get
    // a subtle tint of primaryContainer.
    final bgColor = isSelected
        ? theme.colorScheme.primaryContainer
        : theme.colorScheme.primaryContainer.withValues(alpha: 0.3);

    // Border: selected card gets a primary color border.
    final border = isSelected
        ? Border.all(color: theme.colorScheme.primary, width: 1.5)
        : null;

    // Text weight: today uses bold.
    final fontWeight = isToday ? FontWeight.bold : FontWeight.normal;

    // Day abbreviation (German locale): Mo, Di, Mi, Do, Fr, Sa, So
    final dayAbbr = DateFormat('E', 'de').format(date);
    // Date number
    final dayNum = date.day.toString();

    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // Month label at boundary
        if (isFirstOfMonth)
          Padding(
            padding: EdgeInsets.only(left: leftMargin),
            child: SizedBox(
              width: _kCardWidth + _kCardMargin,
              child: Text(
                DateFormat('MMM', 'de').format(date),
                style: theme.textTheme.labelSmall?.copyWith(
                  color: theme.colorScheme.primary,
                  fontWeight: FontWeight.w600,
                ),
                textAlign: TextAlign.center,
              ),
            ),
          )
        else
          const SizedBox(height: 16), // Reserve space for month label row

        // Day card
        GestureDetector(
          onTap: onTap,
          child: Container(
            width: _kCardWidth,
            height: _kCardHeight,
            margin: EdgeInsets.only(left: leftMargin, right: _kCardMargin),
            decoration: BoxDecoration(
              color: bgColor,
              borderRadius: BorderRadius.circular(8),
              border: border,
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // German day abbreviation
                Text(
                  dayAbbr,
                  style: theme.textTheme.labelSmall?.copyWith(
                    fontWeight: fontWeight,
                    color: isSelected
                        ? theme.colorScheme.onPrimaryContainer
                        : theme.colorScheme.onSurface.withValues(alpha: 0.7),
                  ),
                ),
                const SizedBox(height: 2),
                // Date number
                Text(
                  dayNum,
                  style: theme.textTheme.titleMedium?.copyWith(
                    fontWeight: fontWeight,
                    color: isSelected
                        ? theme.colorScheme.onPrimaryContainer
                        : theme.colorScheme.onSurface,
                  ),
                ),
                // Today accent underline bar
                const SizedBox(height: 4),
                if (isToday)
                  Container(
                    width: 20,
                    height: 2,
                    decoration: BoxDecoration(
                      color: theme.colorScheme.primary,
                      borderRadius: BorderRadius.circular(1),
                    ),
                  )
                else
                  const SizedBox(height: 2),
              ],
            ),
          ),
        ),
      ],
    );
  }
}
