import { isBooleanObject, isNumberObject, isStringObject } from "util/types";
import { IParser, ParserOptions } from "../../interfaces/IParser";
import { Csv } from "../../models/Csv";

export class JsonParserService implements IParser {
  #data: Map<any, any>;
  #parserOptions: ParserOptions;
  constructor(data: any | Map<any, object>, parserOptions?: ParserOptions) {
    this.#parserOptions = parserOptions ?? { single: false, hasId: false };
    if (this.#parserOptions.single && this.#parserOptions.hasId) {
      throw new Error(
        "Can't have the options single as true and hasId as true please go and check the documentation at https://www.npmjs.com/package/nested_json_to_csv"
      );
    }

    if (data instanceof Map) this.#data = data;
    else this.#data = this.#fromJsonToMap(data);
  }
  #fromJsonToMap(json: any): Map<any, any> {
    let mp = new Map<any, any>();
    for (const key in json) {
      if (json[key].constructor == {}.constructor) {
        const newMp = this.#fromJsonToMap(json[key]);
        mp.set(key, newMp);
      } else {
        mp.set(key, json[key]);
      }
    }
    return mp;
  }
  parse(headers: string[]): Csv {
    const csv: Csv = new Csv(headers);
    const body: string = this.#makeBody(csv.getHeaders());
    if (headers[0] != "id" && this.#parserOptions.hasId) {
      const newHeaders: string[] = csv.getHeaders();
      newHeaders.unshift("id");
      csv.setHeaders(newHeaders);
    }
    csv.setbody(body);
    return csv;
  }
  get() {
    return this.#data;
  }
  #makeBody(headers: string[]): string {
    let body: string = "";

    if (this.#parserOptions.single)
      body = this.#getAllData(headers, this.#data);
    else {
      this.#data.forEach((value, key) => {
        if (this.#parserOptions.hasId) {
          body = body.concat(key, ",");
        }
        body = body.concat(this.#getAllData(headers, value), "\n");
      });
    }
    return body;
  }
  #getAllData(headers: string[], data: Map<any, any>): string {
    let body: string = "";
    headers.forEach((path: string, index: number) => {
      body = body.concat(this.#getData(path, data), ",");
    });
    return body;
  }
  #getData(path: string, data: Map<any, any>): string {
    if (path.includes(".")) {
      const key: string = path.substring(0, path.indexOf("."));
      path = path.substring(path.indexOf(".") + 1);
      let newData: Map<any, any>;
      if (data.has(key)) newData = data.get(key);
      else newData = new Map<any, any>();

      return this.#getData(path, newData);
    }
    if (!data.has(path)) {
      if (this.#parserOptions.setDefaultValues) {
        return "";
      }
      console.error(path, data);
      throw Error("Key was not found in your JSON");
    }
    const value: any = data.get(path);
    if (value == undefined || typeof value == "object") {
      throw Error("There is an error in your headers they are not accurate");
    }
    if (isNumberObject(value) || isBooleanObject(value)) {
      return value + ",";
    }
    if (isStringObject(value)) {
      const newValue = value.replace(",", ".");
      return newValue;
    }
    return value;
  }
}
