// AVOID DOM RELATED STUFF HERE! -> allow use in build-time mechanism

import { Data } from "csl-json";
import { Citation } from "./citation";
import { BibReference } from "./bibReference";
import {
  CitationKeyUse,
  BibSortedCitationKeyUse,
  InsertionSortedCitationKeyUse,
} from "./key-use-tracker";
import { BibOrder } from "./order";

export class Bibliography {
  _bib: Map<string, Data>; // hashed and sorted CSL-json data
  _reference_lists: BibReference[] = []; // list of <bib-reference> elements
  _cite_key_use: CitationKeyUse;
  _bibOrder: BibOrder;

  constructor(csl_json: Data[], sorting: (c1: Data, c2: Data) => number) {
    this._bibOrder = { comparison: sorting, inform_citations: false };
    this.bib = csl_json;

    if (this._bibOrder.comparison.name === "insertion") {
      this._cite_key_use = new InsertionSortedCitationKeyUse(new Map());
    } else if (this._bibOrder.inform_citations) {
      this._cite_key_use = new BibSortedCitationKeyUse(
        new Map(),
        new Map(Array.from(this._bib.keys()).map((k, idx) => [k, idx]))
      );
    } else {
      this._cite_key_use = new CitationKeyUse(new Map());
    }
  }

  sort_and_hash(csl_json: Data[], comparison: (c1: Data, c2: Data) => number) {
    return new Map(
      csl_json
        // sort function should be argument of this function and be better
        .sort((a, b) => comparison(a, b))
        .map((citation) => [citation.id, citation])
    );
  }

  set sorting(new_sorting: BibOrder) {
    this._bib = this.sort_and_hash(this.bib, new_sorting.comparison);
    if (
      new_sorting.comparison.name === "insertion" &&
      this._bibOrder.comparison.name != "insertion"
    ) {
      // we need to track insertion order
      this._cite_key_use = new InsertionSortedCitationKeyUse(
        this._cite_key_use.get()
      );
    } else if (new_sorting.inform_citations) {
      this._cite_key_use = new BibSortedCitationKeyUse(
        this._cite_key_use.get(),
        new Map(Array.from(this._bib.keys()).map((k, idx) => [k, idx]))
      );
    } else {
      // we can stop keeping track
      this._cite_key_use = new CitationKeyUse(this._cite_key_use.get());
    }
    this._bibOrder = new_sorting;
  }

  registerCitation(ci: Citation) {
    // important side-effect in if clause!
    if (this._cite_key_use.add(ci).need_ref_update) {
      for (const bib_ref of this._reference_lists) {
        bib_ref.usedReferences = this.used_references();
      }
    }
    ci.bibData = this._bib.get(ci.key);
    console.log(`[Bibliography] Registered ${ci.key}`);
  }

  unregisterCitation(ci: Citation) {
    // important side-effect in if clause!
    if (this._cite_key_use.remove(ci).need_ref_update) {
      for (const bib_ref of this._reference_lists) {
        bib_ref.usedReferences = this.used_references();
      }
    }
  }

  get citations(): Citation[] {
    return this._cite_key_use.citations;
  }

  used_references(): { index: number; csl_data: Data }[] {
    if (this._bibOrder.comparison.name === "insertion") {
      return Array.from(this._cite_key_use.get()).map(([key, entry]) => {
        return { index: entry.index, csl_data: this._bib.get(key) };
      });
    } else {
      return Array.from(this._bib)
        .filter(([key, _]) => this._cite_key_use.has(key))
        .map(([_, csl_data], idx) => {
          return { index: idx, csl_data: csl_data };
        });
    }
  }

  registerReferenceList(bibReference: BibReference) {
    this._reference_lists.push(bibReference);
    bibReference.usedReferences = this.used_references();
    console.log("[Bibliography] Registered ReferenceList");
  }
  unregisterReferenceList(referenceElement) {
    this._reference_lists = this._reference_lists.filter(
      (l) => l != referenceElement
    );
    console.log("[Bibliography] Unregistered ReferenceList");
  }

  set bib(value: Data[]) {
    this._bib = this.sort_and_hash(value, this._bibOrder.comparison);
    console.log("[Bibliography] Sorted & Hashed CSL:", this._bib);
  }
}
