import fs from "fs";
import path from "path";

export function rmdir(dirPath: string): void {
  if (fs.existsSync(dirPath)) {
    fs.readdirSync(dirPath).forEach((entry) => {
      const entryPath = path.join(dirPath, entry);
      if (fs.lstatSync(entryPath).isDirectory()) {
        rmdir(entryPath);
      } else {
        fs.unlinkSync(entryPath);
      }
    });
    fs.rmdirSync(dirPath);
  }
}

export type Maybe<T> = {
  [K in keyof Required<T>]: Pick<T, K> extends Required<Pick<T, K>> ?
    (T[K] extends Date ? Date : Maybe<T[K]>) : (T[K] extends Date ? Exclude<T[K], undefined> : Maybe<Exclude<T[K], undefined>>);
};

export function toMaybe<T extends object>(value: T): Maybe<T> {
  const obj: T & Record<string, any> = value;
  const result: Record<string, any> = {};
  for (const key in obj) {
    result[key] = obj[key] !== undefined ? (obj[key] && typeof obj[key] === "object" ? toMaybe(obj[key]) : obj[key]) : null;
  }
  return result as Maybe<T>;
}

export type PropNameTypeMap = Record<string, { __typename?: string, required?: boolean }>;

export function copyInputToQuery<TQuery extends object, TInput extends object>(query: TQuery, input: TInput, map: PropNameTypeMap): void {
  const dst: Record<string, any> = query;
  const src: TInput & Record<string, any> = input;
  const keys = new Set<string>(Object.keys(dst).concat(Object.keys(src)));
  for (const key of keys) {
    if (src[key] === undefined) {
      continue;
    }
    if (key === "__typename" && !src[key]) {
      continue;
    }
    const propType = map[key];
    if (Array.isArray(src[key])) {
      const dstArray = src[key].map((item: any) => {
        const srcItem = item !== undefined && item !== null && propType && propType.__typename ? { ...item, __typename: propType.__typename } :
          (item !== undefined ? item : null);
        const dstItem = srcItem;
        if (typeof srcItem === "object") {
          copyInputToQuery(dstItem, srcItem, map);
        }
        return dstItem;
      });
      dst[key] = dstArray;
    }
    else if (src[key] !== null && src[key] !== undefined && typeof src[key] === "object") {
      const srcObj = propType && propType.__typename ? { ...src[key], __typename: propType.__typename } : src[key];
      if (dst[key] === null || dst[key] === undefined) {
        dst[key] = srcObj;
      }
      copyInputToQuery(dst[key], srcObj, map);
    }
    else if (propType) {
      if (!propType.required) {
        dst[key] = null;
      }
    }
    else {
      dst[key] = src[key];
    }
  }
}

export type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];

export function removeTrailingSlash(str: string): string {
  return str.endsWith("/") ? str.substring(0, str.length - 1) : str;
}

export function addStartingSlash(str: string): string {
  return str.startsWith("/") ? str : "/" + str;
}

export type PrimitiveType = string | number | bigint | boolean | object | symbol;

export type PrimitiveTypeName = "string" | "number" | "bigint" | "boolean" | "object" | "symbol";

/* eslint-disable valid-typeof */
export function isRecord<T extends PrimitiveType>(value: any, typeName: PrimitiveTypeName): value is Record<string, T> {
  if (!value || typeof value !== "object") {
    return false;
  }
  for (const key in value) {
    if (typeof value[key] !== typeName) {
      return false;
    }
  }
  return true;
}

export function isArray<T extends PrimitiveType>(value: any, typeName: PrimitiveTypeName): value is T[] {
  return Array.isArray(value) && value.every(item => typeof item === typeName);
}
/* eslint-enable valid-typeof */

export function queryToInput<TQuery extends object, TInput extends object>(query: TQuery): TInput {
  if (Array.isArray(query)) {
    const input = query.map((item: any) => item !== undefined && item !== null ? (typeof item === "object" ? queryToInput(item) : item) : null);
    return input as TInput;
  }
  const input: Record<string, any> = {};
  const src = query as Record<string, any>;
  for (const key in src) {
    if (key === "__typename") {
      continue;
    }
    input[key] = src[key] !== undefined && src[key] !== null ? (typeof src[key] === "object" ? queryToInput(src[key]) : src[key]) : null;
  }
  return input as TInput;
}

export function parseJSON<T extends object = object>(json: string | null | undefined): T | undefined {
  if (json) {
    try {
      return JSON.parse(json) as T;
    }
    catch (e) {
      console.error("Error parsing JSON:", e);
    }
  }
  return undefined;
}

export function format(format: string, ...params: any[]): string {
  let result = format;
  for (let i = 0; i < params.length; i++) {
    const regexp = new RegExp(`%${i}`, "g");
    result = result.replace(regexp, params[i]);
  }
  return result;
}

export function copyObject(source: Record<string, any> | null, target: Record<string, any> = {}, excludeProps: string[] = []): object | null {
  if (source === null) {
    return null;
  }
  if (source instanceof Date) {
    return new Date(source);
  }
  if (Array.isArray(source)) {
    return source.map((item: any) => typeof item === "object" ? copyObject(item, undefined, excludeProps) : item);
  }
  for (const key in source) {
    if (source[key] === undefined || excludeProps.includes(key)) {
      continue;
    }
    if (source[key] === null) {
      target[key] = null;
    }
    else if (source[key] instanceof Date) {
      target[key] = new Date(source[key]);
    }
    else if (typeof source[key] === "function" ||
      isInstanceOfClass(source[key])) {
      continue;
    }
    else if (Array.isArray(source[key])) {
      target[key] = copyObject(source[key], undefined, excludeProps);
    }
    else if (typeof source[key] === "object") {
      if (!target[key]) {
        target[key] = {};
      }
      copyObject(source[key], target[key], excludeProps);
    }
    else {
      target[key] = source[key];
    }
  }
  return target;
}

export function isInstanceOfClass(value: any): boolean {
  return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && value.constructor.name !== "Object";
}

export function getLocalStorage(name: string): string | undefined {
  try {
    return window.localStorage.getItem("Solid." + name) ?? undefined;
  }
  catch (e) {
    console.error(e);
    return undefined;
  }
}

export function setLocalStorage(name: string, value: string | undefined): boolean {
  try {
    if (value !== undefined) {
      window.localStorage.setItem("Solid." + name, value);
    }
    else {
      window.localStorage.removeItem("Solid." + name);
    }
    return true;
  }
  catch (e) {
    console.error(e);
    return false;
  }
}

export function getLocalStorageKeys(): string[] {
  const keys: string[] = [];
  try {
    for (let i = 0; i < window.localStorage.length; i++) {
      const key = window.localStorage.key(i);
      if (key && key.startsWith("Solid.")) {
        keys.push(key.substring("Solid.".length));
      }
    }
  }
  catch (e) {
    console.error(e);
  }
  return keys;
}

export function round(value: number, digits: number = 2): number {
  return Math.round((value + Number.EPSILON) * Math.pow(10, digits)) / Math.pow(10, digits);
}
