import 'dart:async';
import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:mnemo_cards/features/packs/images_holder.dart';
import 'package:mnemo_cards/main.dart';
import 'package:mnemo_cards/utils/iterable_helper.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:rxdart/rxdart.dart';

import '../../di/locator.dart';

class CardsViewController extends ChangeNotifier {
  bool _expanded = false;
  bool _favorite = false;
  double imageWidth = 0.0;
  List<GameCardDto> cards = [];
  List<int> allCardsOrder = [];
  List<int> favoriteCardsOrder = [];

  List<int> get cardsOrder => _favorite ? favoriteCardsOrder : allCardsOrder;

  final CardPackDto cardPack;

  final ScrollController _cardsScrollController =
      ScrollController(keepScrollOffset: true);

  ScrollController get cardsScrollController => _cardsScrollController;

  StreamController<CardsViewState> _streamController =
      StreamController.broadcast();

  Stream<CardsViewState> get asStream =>
      _streamController.stream.startWith(state).distinct();

  Stream<bool> get expandedStream => asStream.map((s) => s.expanded).distinct();

  Stream<bool> get favoriteStream => asStream.map((s) => s.favorite).distinct();

  Stream<List<GameCardDto>> get cardsStream =>
      asStream.map((s) => s.packCards).distinct((p, n) =>
          (p.length == n.length) &&
          p.indexed.every((p) => n[p.$1].id == p.$2.id));

  CardsViewState get state => CardsViewState(
        expanded: _expanded,
        favorite: _favorite,
        packCards: cards,
      );

  List<GameCardDto> _filterPackCards() => cardPack.cards
      .where(
        (card) =>
            !_favorite ||
            locator.favoriteCardsController.isFavoriteCard(card.id),
      )
      .toList();

  void setExpanded(bool expanded) {
    if (cards.isNotEmpty) {
      _expanded = expanded;
      notifyListeners();
    }
  }

  Future<void> _saveCardsOrder() async {
    if (_favorite) {
      await globalSharedPreferences.setString(
        'sort_fav_${cardPack.id}',
        favoriteCardsOrder.encode(),
      );
    } else {
      await globalSharedPreferences.setString(
        'sort_${cardPack.id}',
        allCardsOrder.encode(),
      );
    }
  }

  void _loadCardsOrder() {
    favoriteCardsOrder = globalSharedPreferences
            .getString(
              'sort_fav_${cardPack.id}',
            )
            ?.decodeList<int>() ??
        [];
    allCardsOrder = globalSharedPreferences
            .getString(
              'sort_${cardPack.id}',
            )
            ?.decodeList<int>() ??
        [];
    if (allCardsOrder.isEmpty) {
      allCardsOrder = cardPack.cards.map((c) => c.id).toList();
    }
    if (favoriteCardsOrder.isEmpty) {
      favoriteCardsOrder = allCardsOrder.toList();
    }
  }

  void setCardsOrder(List<int> order) {
    if (_favorite) {
      favoriteCardsOrder = order.toList();
    } else {
      allCardsOrder = order.toList();
    }
    _saveCardsOrder();
    _updateCards();
  }

  void onOrderChanged(int from, int to) {
    if (_favorite) {
      favoriteCardsOrder.move(from, to);
    } else {
      allCardsOrder.move(from, to);
    }
    _saveCardsOrder();
    _updateCards();
  }

  void setFavorite(bool favorite) {
    _favorite = favorite;
    _updateCards();
    notifyListeners();
  }

  void _addToStream() {
    _streamController.add(state);
  }

  double get offset => _cardsScrollController.offset;

  int get offsetCards => (_cardsScrollController.offset / imageWidth).floor();

  void toggleExpanded() => setExpanded(!_expanded);

  CardsViewController(this.cardPack) {
    addListener(_addToStream);
    _loadCardsOrder();
    _updateCards();
  }

  void _updateCards() {
    cards = _filterPackCards();
    cards
      ..sort((p, n) {
        final pIndex = cardsOrder.indexOf(p.id);
        final nIndex = cardsOrder.indexOf(n.id);
        if (pIndex == -1) {
          return nIndex == -1 ? p.id.compareTo(n.id) : 1;
        } else if (nIndex == -1) {
          return -1;
        }
        return pIndex.compareTo(nIndex);
      });
    notifyListeners();
  }

  Future<void> animateTo(int index) {
    final millis =
        ((_cardsScrollController.offset / imageWidth - index).abs() * 30.0)
            .ceil();
    return _cardsScrollController.animateTo(
      imageWidth * index,
      duration: Duration(milliseconds: max(millis, 100)),
      curve: Curves.easeInOut,
    );
  }

  void jumpTo(int index) {
    return _cardsScrollController.jumpTo(
      imageWidth * index,
    );
  }
}

class CardsViewState {
  final bool expanded;
  final bool favorite;
  final List<GameCardDto> packCards;

  CardsViewState({
    required this.expanded,
    required this.favorite,
    required this.packCards,
  });

  @override
  int get hashCode => expanded.hashCode ^ expanded.hashCode ^ packCards.length;

  @override
  bool operator ==(Object other) {
    return (other is CardsViewState) &&
        expanded == other.expanded &&
        favorite == other.favorite &&
        (packCards.length == other.packCards.length) &&
        packCards.indexed.every((p) => other.packCards[p.$1].id == p.$2.id);
  }
}
