import 'dart:convert';

import 'package:sendtrain/daos/actions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/helpers/date_time_helpers.dart';

class ActionModel {
  final ActionsDao dao;
  List<Item> items;
  Action action;

  ActionModel({required this.action, required AppDatabase db})
      : dao = ActionsDao(db),
        items = _generateItems(action);

  int get id => action.id;
  ActionStatus get status => action.status;
  Map get state => json.decode(action.state);
  List<Set> get sets => items.whereType<Set>().toList();
  List<Item> get allItems => _flattenedItems();
  int get totalTime {
    int time = 0;
    for (int i = 0; i < allItems.length; i++) {
      Item item = allItems[i];
      time += item.time ?? 0;
    }

    return toSeconds(time);
  }

  List<Item> _flattenedItems() {
    List<Item> items = [];

    for (int i = 0; i < this.items.length; i++) {
      Item item = this.items[i];
      if (item.runtimeType == Set) {
        Set setItem = item as Set;
        for (int j = 0; j < setItem.items.length; j++) {
          items.add(setItem.items[j]);
        }
      } else {
        items.add(item);
      }
    }

    return items;
  }

  static List<Item> _generateItems(Action action) {
    int totalItems = 0;
    int setItems = 0;
    List<Item> items = [];
    final List setReps = json.decode(action.totalReps);

    if (action.restBeforeSets != null) {
      items.add(Rest(
          id: totalItems,
          position: totalItems,
          action: action,
          time: action.restBeforeSets!,
          name: 'prepare'));
    }

    for (int i = 0; i < action.totalSets; i++) {
      final int totalReps;

      if (setReps.length == 1) {
        totalReps = setReps.first;
      } else {
        totalReps = setReps[i];
      }

      totalItems += 1;
      items.add(Set(
          id: totalItems,
          setOrder: setItems++,
          position: totalItems,
          action: action,
          totalReps: totalReps));

      if (action.restBetweenSets != null && i < action.totalSets - 1) {
        totalItems += 1;
        items.add(Rest(
            id: totalItems,
            position: totalItems,
            action: action,
            time: action.restBetweenSets!,
            name: 'rest'));
      }
    }

    if (action.restAfterSets != null && totalItems != items.length) {
      totalItems += 1;
      items.add(Rest(
          id: totalItems,
          position: totalItems,
          action: action,
          time: action.restAfterSets!,
          name: 'cooldown'));
    }

    return items;
  }

  Future<Action> updateStatus(ActionStatus status) async {
    Action newAction = action.copyWith(id: action.id, status: status);
    await dao.createOrUpdate(newAction.toCompanion(true));
    action = newAction;
    return newAction;
  }

  Future<Action> updateState(String state) async {
    Action newAction = action.copyWith(id: action.id, state: state);
    await dao.createOrUpdate(newAction.toCompanion(true));
    action = newAction;
    return newAction;
  }
}

class Item {
  final int id;
  final Action action;
  int position;
  List<Item> items = [];
  dynamic value;
  final String name;
  int? parentId;
  int? time;

  Item(
      {required this.id,
      required this.position,
      required this.action,
      this.parentId,
      this.time})
      : name = action.title;

  RepType get valueType => action.repType;
  String get humanValueType => valueType == RepType.time ? 'seconds' : 'reps';
}

class Set extends Item {
  final int totalReps;
  int? setOrder;

  Set(
      {required super.id,
      required super.action,
      required super.position,
      required this.totalReps,
      this.setOrder}) {
    items = _generateItems(action, id, totalReps);
  }

  int? get weightMultiplyer =>
      action.setWeights != null ? json.decode(action.setWeights!)[id] : null;
  List<Reps> get reps => items.whereType<Reps>().toList();

  static List<Item> _generateItems(action, id, totalReps) {
    List<Item> items = [];
    // add item for exercise
    int position = 0;

    if (action.repType == RepType.time) {
      for (int i = 0; i < totalReps; i++) {
        position = position > 0 ? position + 1 : position;

        // don't show a rest before first rep
        if (i > 0) {
          items.add(Rest(
              id: position,
              position: position,
              parentId: id,
              action: action,
              time: action.restBetweenReps,
              name: 'rest'));
        }

        items.add(Reps(
            id: ++position, position: position, parentId: id, action: action));

        if (action.isAlternating) {
          items.add(Rest(
              id: ++position,
              position: position,
              parentId: id,
              action: action,
              time: action.restBetweenReps,
              name: 'alternate'));
          items.add(Reps(
              id: ++position,
              position: position,
              parentId: id,
              action: action));
        }
      }
    } else {
      items.add(Reps(id: id, position: position, action: action));

      if (action.isAlternating) {
        items.add(Rest(
            id: ++position,
            position: position,
            parentId: id,
            action: action,
            time: action.restBetweenReps,
            name: 'alternate'));
        items.add(Reps(id: id, position: ++position, action: action));
      }
    }

    return items;
  }
}

class Reps extends Item {
  Reps(
      {required super.id,
      required super.position,
      required super.action,
      super.parentId});

  @override
  dynamic get value => type == RepType.time ? time : count;

  RepType get type => action.repType;
  @override
  int? get time => toSeconds(action.repLength!);
  int? get count => getReps(id, json.decode(action.totalReps));
  int? get weight =>
      action.repWeights != null ? json.decode(action.repWeights!)[id] : null;

  static int getReps(setId, reps) {
    if (reps.length > 1) {
      return reps[setId];
    } else {
      return reps.first;
    }
  }
}

class Rest extends Item {
  @override
  String name;

  Rest(
      {required super.id,
      required super.position,
      required super.action,
      super.parentId,
      required super.time,
      required this.name});

  // @override
  // String get name => 'Rest';
  @override
  int get value => toSeconds(time ?? 0);
  @override
  RepType get valueType => RepType.time;
}