import { Countries, CountriesData, States } from "./countries";
import { CountryNames } from "./countryNames";
import { StateNames } from "./stateNames";

export interface InputCountry {
  alpha2Code?: string;
  alpha3Code?: string;
  altSpellings?: string[];
  area?: number | null;
  borders?: string[];
  callingCodes?: string[];
  capital?: string | null;
  currencies?: Array<{
    code: string | null;
    name: string | null;
    symbol: string | null;
  }>;
  demonym?: string | null;
  flag?: string | null;
  gini?: number | null;
  languages?: Array<{
    iso639_1?: string | null;
    iso639_2?: string | null;
    name?: string | null;
    nativeName?: string | null;
  }>;
  latlng?: [number, number] | null;
  name?: string;
  nativeName?: string | null;
  numericCode?: string | null;
  population?: number | null;
  region?: string | null;
  regionalBlocs?: Array<{
    acronym: string;
    name: string;
    otherNames?: string[];
    otherAcronyms?: string[];
  }>;
  subregion?: string | null;
  timezones?: string[];
  topLevelDomain?: string[];
  translations?: {
    br: string | null;
    de: string | null;
    es: string | null;
    fa: string | null;
    fr: string | null;
    hr: string | null;
    it: string | null;
    ja: string | null;
    nl: string | null;
    pt: string | null;
  };
  cioc?: string | null;
  states?: States;
}

export class EpicGeo {
  protected CustomData: Record<string, InputCountry> = {};

  public custom = (
    countryName: ReturnType<CountryNames["get"]>[number],
    data: InputCountry
  ) => {
    this.CustomData[countryName] = data;

    return this;
  };

  public countries = (): Countries => ({
    ...CountriesData,
    ...this.CustomData,
  });

  public country = (countryName: ReturnType<CountryNames["get"]>[number]) =>
    this.countries()[countryName];

  public countryList = (): Array<ReturnType<CountryNames["get"]>[number]> =>
    Object.keys(this.countries()) as any;

  public hasCountry = (name: string) =>
    this.countryList()
      .map((country) => country.toLowerCase())
      .includes(name.toLowerCase() as any);

  public states = (countryName?: ReturnType<CountryNames["get"]>[number]) =>
    countryName
      ? this.countries()[countryName]?.states
      : Object.values(this.countries())
          .map((country) => country.states)
          .reduce((p, c) => ({ ...p, ...c }), {});

  public state = (
    countryName: ReturnType<CountryNames["get"]>[number],
    stateName: ReturnType<StateNames["get"]>[number]
  ) => this.states(countryName)?.[stateName];

  public stateList = (): ReturnType<StateNames["get"]>[number] =>
    Object.values(this.countries()).reduce(
      (p, c) => [...p, ...Object.keys(c.states)],
      [] as string[]
    ) as any;

  public hasState = (
    name: string,
    countryName?: ReturnType<CountryNames["get"]>[number]
  ) =>
    ((countryName
      ? Object.keys(this.country(countryName)?.states || {})
      : (this.stateList() as any)) as string[])
      .map((state) => state.toLowerCase())
      .includes(name.toLowerCase());

  public cities = (
    countryName?: ReturnType<CountryNames["get"]>[number],
    stateName?: ReturnType<StateNames["get"]>[number]
  ) =>
    countryName
      ? stateName
        ? this.state(countryName, stateName)?.cities || []
        : Object.values(this.states(countryName))
            .map((state) => state.cities)
            .reduce((p, c) => p.concat(c), [])
      : Object.values(this.states(countryName))
          .map((state) => state.cities)
          .reduce((p, c) => p.concat(c), []);

  public cityList = () =>
    Object.values(this.countries())
      .map((country) =>
        Object.values(country.states)
          .map((state) => state?.cities || [])
          .reduce((c, p) => c.concat(p), [])
      )
      .reduce((c, p) => c.concat(p), []);

  public hasCity = (
    name: string,
    countryName?: ReturnType<CountryNames["get"]>[number],
    stateName?: ReturnType<StateNames["get"]>[number]
  ) =>
    (countryName
      ? stateName
        ? this.state(countryName, stateName)?.cities || []
        : Object.values(this.states(countryName) || {})
            .map((state) => state.cities)
            .reduce((p, c) => p.concat(c), [])
      : this.cityList()
    )
      .map((city) => city.toLowerCase())
      .includes(name.toLowerCase());
}
