import type { JSONSchemaType } from "ajv";
import {
  splitKey,
  type NestedDatabaseType,
  type NestedValue,
  toNested,
} from "@orbitdb/nested-db";

import {
  DBElements,
  ExtractKeys,
  ExtractKeysAsList,
  GetValueFromKey,
  GetValueFromKeyList,
  RecursivePartial,
} from "./types.js";
import { removeUndefinedProperties } from "./utils.js";

export type TypedNested<T extends NestedValue> = Omit<
  NestedDatabaseType,
  "put" | "set" | "putNested" | "del" | "get" | "all"
> & {
  put<K extends ExtractKeys<T>>(
    key: K,
    value: GetValueFromKey<T, K>,
  ): Promise<string>;
  put<K extends ExtractKeysAsList<T>>(
    key: K,
    value: GetValueFromKeyList<T, K>,
  ): Promise<string>;
  set: TypedNested<T>["put"];
  putNested(value: RecursivePartial<T>): Promise<string[]>;
  putNested<K extends ExtractKeys<T>>(
    key: K,
    value: GetValueFromKey<T, K>,
  ): Promise<string[]>;
  setNested: TypedNested<T>["putNested"];
  del<K extends ExtractKeys<T>>(key: K): Promise<string>;
  get<K extends ExtractKeys<T>>(
    key: K,
  ): Promise<GetValueFromKey<T, K> | undefined>;
  all: () => Promise<
    {
      key: ExtractKeys<T>;
      value: GetValueFromKey<T, ExtractKeys<T>>;
      hash: string;
    }[]
  >;
  allAsJSON(): Promise<T>;
};

export const typedNested = <T extends NestedValue>({
  db,
  schema,
}: {
  db: NestedDatabaseType;
  schema: JSONSchemaType<RecursivePartial<T>>;
}): TypedNested<T> => {
  const supportedKey = (key: string | string[]) => {
    const keyComponents = typeof key === "string" ? splitKey(key) : key;
    let schemaBranch = schema;
    for (const k of keyComponents) {
      if (schemaBranch.additionalProperties) return true;
      if (schemaBranch.properties[k] === undefined) return false;
      schemaBranch = schemaBranch.properties[k];
    }
    return true;
  };
  // const compiledValidators: {[key in ExtractKeys<T>]: (x: unknown)=>x is GetValueFromKey<T, key>} = {};

  /*const validateKeyValue = <K extends ExtractKeys<T>>(
    val: unknown, key: K
  ): val is GetValueFromKey<T, K> => {
    return compiledValidators[key](val)
  }*/
  return new Proxy(db, {
    get(target, prop) {
      if (prop === "allAsJSON") {
        // Todo: type check
        const typedAllAsJSON: TypedNested<T>["allAsJSON"] = async () =>
          toNested(await db.all()) as T;
        return typedAllAsJSON;
      } else if (prop === "setNested" || prop === "putNested") {
        const typedSetNested: TypedNested<T>["setNested"] = async <
          K extends ExtractKeys<T>,
        >(
          keyOrValue: K | RecursivePartial<T>,
          value?: GetValueFromKey<T, K>,
        ): Promise<string[]> => {
          if (typeof keyOrValue === "string") {
            // @ts-expect-error types in progress
            const data = removeUndefinedProperties(value);
            // Todo: type check
            return await db.putNested(keyOrValue, data);
          } else {
            // @ts-expect-error types in progress
            const data = removeUndefinedProperties(keyOrValue);
            // Todo: type check
            return await db.putNested(data);
          }
        };
        return typedSetNested;
      }

      /*if (prop === "get") {
        return async <K extends ExtractKeys<T>>(key: K): Promise<GetValueFromKey<T, K> | undefined> => {
          if (!supportedKey(key)) throw new Error(`Unsupported key ${key}.`);
          const val = await target.get(key);
          if (val === undefined) return val;
          if (validateKeyValue(val, key)) {
            return val
          } else {
            return undefined;
          };
        };
      }
      else {
      };*/
      return target[prop as keyof typeof target];
    },
  }) as unknown as TypedNested<T>;
};
