import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../data/variety_repository.dart';
import '../db/enums.dart';
import '../domain/crop_calendar.dart';

/// Inventory list state: all items from the DB plus the current search query
/// and active filters. [visibleItems] applies query ∧ category ∧ form; grouping
/// by category is done in the UI. Empty filter sets mean "show everything".
class InventoryState extends Equatable {
  const InventoryState({
    this.items = const [],
    this.drafts = const [],
    this.query = '',
    this.categoryFilter = const {},
    this.typeFilter = const {},
    this.organicOnly = false,
    this.needsReproductionOnly = false,
    this.sharingOnly = false,
    this.sowThisMonthOnly = false,
    this.filterMonth = 0,
    this.loading = true,
    this.selectionMode = false,
    this.selectedIds = const {},
  });

  final List<VarietyListItem> items;

  /// Photo-first captures still awaiting a name — the "to catalogue" tray,
  /// shown above the list and never mixed into [items].
  final List<VarietyListItem> drafts;

  final String query;

  /// Categories to keep; empty = all categories.
  final Set<String> categoryFilter;

  /// Lot forms to keep; empty = all forms. An item matches if it holds at
  /// least one lot of a selected form.
  final Set<LotType> typeFilter;

  /// When true, keep only varieties flagged organic ("eco").
  final bool organicOnly;

  /// When true, keep only varieties flagged "needs reproducing this season".
  final bool needsReproductionOnly;

  /// When true, keep only varieties with some lot offered to others — the
  /// "what I share" view.
  final bool sharingOnly;

  /// When true, keep only varieties whose recorded sow-months include
  /// [filterMonth] — the inventory "to sow this month" view.
  final bool sowThisMonthOnly;

  /// The month (1..12) the "to sow this month" filter matches against — set to
  /// the current month by the cubit when the filter is turned on. Only read
  /// while [sowThisMonthOnly] is true.
  final int filterMonth;

  final bool loading;

  /// Whether the list is in multi-select mode — used to pick a subset of the
  /// inventory to print labels for.
  final bool selectionMode;

  /// Ids of the varieties currently selected in [selectionMode].
  final Set<String> selectedIds;

  /// Categories present across all items, in display order (deduped), so the
  /// UI can offer one chip per category actually in use.
  List<String> get categories {
    final seen = <String>{};
    final result = <String>[];
    for (final item in items) {
      final category = item.category;
      if (category != null && seen.add(category)) result.add(category);
    }
    return result;
  }

  List<VarietyListItem> get visibleItems {
    final q = query.trim().toLowerCase();
    return items.where((i) {
      if (q.isNotEmpty && !i.label.toLowerCase().contains(q)) return false;
      if (categoryFilter.isNotEmpty && !categoryFilter.contains(i.category)) {
        return false;
      }
      if (typeFilter.isNotEmpty &&
          i.lotTypes.intersection(typeFilter).isEmpty) {
        return false;
      }
      if (organicOnly && !i.isOrganic) return false;
      if (needsReproductionOnly && !i.needsReproduction) return false;
      if (sharingOnly && !i.isShared) return false;
      if (sowThisMonthOnly && !maskHasMonth(i.sowMonths, filterMonth)) {
        return false;
      }
      return true;
    }).toList();
  }

  /// Whether any variety has recorded sow-months, so the UI only offers the
  /// "to sow this month" chip when it would match something.
  bool get hasSowCalendar => items.any((i) => i.sowMonths != null);

  /// Whether any item is flagged organic, so the UI only offers the eco filter
  /// chip when it would actually match something.
  bool get hasOrganic => items.any((i) => i.isOrganic);

  /// Whether any item is flagged "needs reproducing", so the UI only offers
  /// that filter chip when it would match something.
  bool get hasNeedsReproduction => items.any((i) => i.needsReproduction);

  /// Whether any variety has a lot offered to others, so the UI only offers
  /// the "sharing" chip and the printable catalog when they'd show something.
  bool get hasShared => items.any((i) => i.isShared);

  InventoryState copyWith({
    List<VarietyListItem>? items,
    List<VarietyListItem>? drafts,
    String? query,
    Set<String>? categoryFilter,
    Set<LotType>? typeFilter,
    bool? organicOnly,
    bool? needsReproductionOnly,
    bool? sharingOnly,
    bool? sowThisMonthOnly,
    int? filterMonth,
    bool? loading,
    bool? selectionMode,
    Set<String>? selectedIds,
  }) {
    return InventoryState(
      items: items ?? this.items,
      drafts: drafts ?? this.drafts,
      query: query ?? this.query,
      categoryFilter: categoryFilter ?? this.categoryFilter,
      typeFilter: typeFilter ?? this.typeFilter,
      organicOnly: organicOnly ?? this.organicOnly,
      needsReproductionOnly:
          needsReproductionOnly ?? this.needsReproductionOnly,
      sharingOnly: sharingOnly ?? this.sharingOnly,
      sowThisMonthOnly: sowThisMonthOnly ?? this.sowThisMonthOnly,
      filterMonth: filterMonth ?? this.filterMonth,
      loading: loading ?? this.loading,
      selectionMode: selectionMode ?? this.selectionMode,
      selectedIds: selectedIds ?? this.selectedIds,
    );
  }

  @override
  List<Object?> get props => [
    items,
    drafts,
    query,
    categoryFilter,
    typeFilter,
    organicOnly,
    needsReproductionOnly,
    sharingOnly,
    sowThisMonthOnly,
    filterMonth,
    loading,
    selectionMode,
    selectedIds,
  ];
}

/// Subscribes to the repository's reactive inventory stream. The list updates
/// automatically after a quick-add — no manual refresh.
class InventoryCubit extends Cubit<InventoryState> {
  InventoryCubit(this._repo, {int Function()? nowMonth})
      : _nowMonth = nowMonth ?? (() => DateTime.now().month),
        super(const InventoryState()) {
    // One combined subscription (list + draft tray). Two separate StreamGroups
    // here re-emit in a loop that hangs widget tests — see watchInventoryView.
    _sub = _repo.watchInventoryView().listen(
      (view) => emit(
        state.copyWith(items: view.items, drafts: view.drafts, loading: false),
      ),
    );
  }

  final VarietyRepository _repo;
  final int Function() _nowMonth;
  late final StreamSubscription<
    ({List<VarietyListItem> items, List<VarietyListItem> drafts})
  >
  _sub;

  void search(String query) => emit(state.copyWith(query: query));

  /// Toggles a category in the filter (add if absent, remove if present).
  void toggleCategory(String category) {
    final next = Set<String>.of(state.categoryFilter);
    if (!next.remove(category)) next.add(category);
    emit(state.copyWith(categoryFilter: next));
  }

  /// Toggles a lot form in the filter (add if absent, remove if present).
  void toggleType(LotType type) {
    final next = Set<LotType>.of(state.typeFilter);
    if (!next.remove(type)) next.add(type);
    emit(state.copyWith(typeFilter: next));
  }

  /// Toggles the "organic only" filter.
  void toggleOrganicOnly() =>
      emit(state.copyWith(organicOnly: !state.organicOnly));

  /// Toggles the "needs reproducing only" filter.
  void toggleNeedsReproductionOnly() =>
      emit(state.copyWith(needsReproductionOnly: !state.needsReproductionOnly));

  /// Toggles the "what I share" filter.
  void toggleSharingOnly() =>
      emit(state.copyWith(sharingOnly: !state.sharingOnly));

  /// Toggles the "to sow this month" filter, pinned to the current month.
  void toggleSowThisMonth() => emit(state.copyWith(
        sowThisMonthOnly: !state.sowThisMonthOnly,
        filterMonth: _nowMonth(),
      ));

  /// Enters multi-select mode with an empty selection (from the toolbar).
  void startSelection() =>
      emit(state.copyWith(selectionMode: true, selectedIds: const {}));

  /// Enters multi-select mode with [id] as the first selected variety
  /// (triggered by a long-press on a tile).
  void enterSelection(String id) =>
      emit(state.copyWith(selectionMode: true, selectedIds: {id}));

  /// Toggles a variety in the selection (add if absent, remove if present).
  /// An empty selection stays in selection mode; the user exits explicitly.
  void toggleSelection(String id) {
    final next = Set<String>.of(state.selectedIds);
    if (!next.remove(id)) next.add(id);
    emit(state.copyWith(selectionMode: true, selectedIds: next));
  }

  /// Selects every currently visible variety, so "filter to a category, then
  /// select all" is the way to pick a subset of the inventory to print.
  void selectAllVisible() => emit(
    state.copyWith(
      selectionMode: true,
      selectedIds: state.visibleItems.map((i) => i.id).toSet(),
    ),
  );

  /// Leaves selection mode and clears the selection.
  void exitSelection() =>
      emit(state.copyWith(selectionMode: false, selectedIds: const {}));

  /// Clears all filters (search is left untouched).
  void clearFilters() => emit(
    state.copyWith(
      categoryFilter: const {},
      typeFilter: const {},
      organicOnly: false,
      needsReproductionOnly: false,
      sharingOnly: false,
      sowThisMonthOnly: false,
    ),
  );

  @override
  Future<void> close() async {
    await _sub.cancel();
    return super.close();
  }
}
