export interface ICollection<T> {
  items: T[];
  totalCount: number;

  toArray(): T[];
}

export class Collection<T> implements ICollection<T> {
  protected _items: Map<number, T>;
  protected _totalCount: number | undefined = undefined;
  protected _totalCountIsProvided = false;

  get totalCount(): number {
    if (this._totalCountIsProvided) {
      return Number(this._totalCount);
    }

    return Array.from(this._items.values()).reduce(
      (total, item) => (item !== undefined ? total + 1 : total),
      0
    );
  }

  get items(): T[] {
    return Array.from(this._items.values()).filter((item) => item);
  }

  constructor(initialValues?: T[], totalCount?: number) {
    this._items = new Map<number, T>(
      initialValues
        ? initialValues.map((value: any, index: number) => [index, value] as [number, T])
        : []
    );

    this._totalCountIsProvided = typeof totalCount === "number";
    if (this._totalCountIsProvided) {
      this._totalCount = Number(totalCount);
    }
  }

  private incTotalCount() {
    if (this._totalCountIsProvided) {
      this._totalCount = Number(this._totalCount) + 1;
    }
  }

  private decTotalCount() {
    if (this._totalCountIsProvided) {
      this._totalCount = Number(this._totalCount) - 1;
    }
  }

  private resetTotalCount() {
    this._totalCount = 0;
  }

  /*public static createEmpty() {
        return new Collection([]);
    }

    public static create(initialValues: any[], totalCount?: number) {
        return new Collection(initialValues, totalCount);
    }*/

  public reset() {
    this._items.clear();
    this.resetTotalCount();
  }

  public addItem(item: T) {
    this._items.set(this._items.size, item);
    this.incTotalCount();
  }

  public replaceItem(index: number, item: T) {
    this._items.set(index, item);
  }

  public removeByIndex(index: number) {
    this._items.delete(index); // <--- this._items.set(index, undefined);
    this.decTotalCount();
  }

  public addCollection(collection: ICollection<T>) {
    if (collection) {
      collection.items.forEach((item: T) => {
        this.addItem(item);
      });
    }
  }

  public find(predicate: (value: T, index: number, obj: T[]) => unknown): T | undefined {
    return Array.from(this._items.values()).find(predicate);
  }

  public toArray(): T[] {
    return this.items;
  }

  public toString(): string {
    return this.items
      .map((item) => (item !== undefined ? JSON.stringify(item) : undefined))
      .toString();
  }

  public toStringArray(): string[] {
    return Array.from(this._items.values(), (element) => JSON.stringify(element)).filter(
      (element) => element.length > 0
    );
  }
}
