import type { IEntry, IValue, ListingResult } from "./types";

// export function listingAsOrdered(listing: ListingResult): OrderedListing {
//   const entries = Object.entries(listing) as [Address, IEntry][];
//   return entries
//     .sort(([_, a], [__, b]) =>
//       String(a.value.c).localeCompare(String(b.value.c))
//     )
//     .sort(([_, a], [__, b]) =>
//       String(a.value.t).localeCompare(String(b.value.t))
//     )
//     .sort(([_, a], [__, b]) => a.attribute.localeCompare(b.attribute));
// }

export class UpListing {
  public readonly entries: UpEntry[];
  private _objects: { [key: string]: UpObject } = {};

  constructor(listing: ListingResult) {
    this.entries = Object.entries(listing).map(
      (lr) => new UpEntry(...lr, this)
    );
  }

  public get objects() {
    const allEntities = new Set(this.entries.map((e) => e.entity));
    const result: { [key: string]: UpObject } = {};
    Array.from(allEntities).forEach(
      (entity) => (result[entity] = new UpObject(entity, this))
    );
    return result;
  }

  public getObject(address: string) {
    if (!this._objects[address]) {
      this._objects[address] = new UpObject(address, this);
    }
    return this._objects[address];
  }
}

export class UpObject {
  public readonly address;
  public listing: UpListing | undefined;

  constructor(address: string, listing?: UpListing) {
    this.address = address;
    this.listing = listing;
  }

  public bind(listing: UpListing) {
    this.listing = listing;
  }

  public get attributes() {
    return (this.listing?.entries || []).filter(
      (e) => e.entity === this.address
    );
  }

  public get backlinks() {
    return (this.listing?.entries || []).filter(
      (e) => e.value.c === this.address
    );
  }

  public get attr() {
    const result = {} as { [key: string]: UpEntry[] };
    this.attributes.forEach((entry) => {
      if (!result[entry.attribute]) {
        result[entry.attribute] = [];
      }

      result[entry.attribute].push(entry);
    });

    return result;
  }

  public get(attr: string) {
    return this.attr[attr] ? this.attr[attr][0].value.c : undefined;
  }

  public identify(): string[] {
    const lblValues = (this.attr["LBL"] || []).map((e) => String(e.value.c));
    return lblValues;
  }

  public asDict() {
    return {
      address: this.address,
      attributes: this.attr,
    };
  }
}

export class UpEntry extends UpObject implements IEntry {
  entity: string;
  attribute: string;
  value: IValue;
  provenance: string;
  timestamp: string;

  constructor(address: string, entry: IEntry, listing: UpListing) {
    super(address, listing);

    this.entity = entry.entity;
    this.attribute = entry.attribute;
    this.value = entry.value;
    this.provenance = entry.provenance;
    this.timestamp = entry.timestamp;
  }

  public toString(): string {
    return `(${this.entity} ${this.attribute} ${this.value.c} [${this.value.t}])`;
  }
}
