import { omitKeys } from "bknd/utils";
import type { EntityManager } from "data/entities";
import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/utils";

export const jsonFieldConfigSchema = s
   .strictObject({
      default_value: s.any(),
      ...omitKeys(baseFieldConfigSchema.properties, ["default_value"]),
   })
   .partial();

export type JsonFieldConfig = s.Static<typeof jsonFieldConfigSchema>;

export class JsonField<Required extends true | false = false, TypeOverride = object> extends Field<
   JsonFieldConfig,
   TypeOverride,
   Required
> {
   override readonly type = "json";

   protected getSchema() {
      return jsonFieldConfigSchema;
   }

   /**
    * Transform value after retrieving from database
    * @param value
    */
   override transformRetrieve(value: any): any {
      const val = super.transformRetrieve(value);

      if (val === null && this.hasDefault()) {
         return this.getDefault();
      }

      if (this.isSerialized(val)) {
         return JSON.parse(val);
      }

      return val;
   }

   isSerializable(value: any) {
      try {
         const stringified = JSON.stringify(value);
         if (stringified === JSON.stringify(JSON.parse(stringified))) {
            return true;
         }
      } catch (e) {}

      return false;
   }

   isSerialized(value: any) {
      try {
         if (typeof value === "string") {
            return value === JSON.stringify(JSON.parse(value));
         }
      } catch (e) {}

      return false;
   }

   override isValid(value: any): boolean {
      return this.isSerializable(value);
   }

   override getValue(value: any, context: TRenderContext): any {
      switch (context) {
         case "table":
            if (value === null) return null;
            return JSON.stringify(value);
         case "submit":
            if (!value || (typeof value === "string" && value.length === 0)) {
               return null;
            } else if (typeof value === "object") {
               return value;
            }

            try {
               return JSON.parse(value);
            } catch (e) {
               return value;
            }
      }

      return value;
   }

   override async transformPersist(
      _value: any,
      em: EntityManager<any>,
      context: TActionContext,
   ): Promise<string | undefined> {
      const value = await super.transformPersist(_value, em, context);
      if (this.nullish(value)) return value;

      if (!this.isSerializable(value)) {
         throw new TransformPersistFailedException(
            `Field "${this.name}" must be serializable to JSON.`,
         );
      }

      if (this.isSerialized(value)) {
         return value;
      }

      return JSON.stringify(value);
   }

   override toType(): TFieldTSType {
      return {
         ...super.toType(),
         type: "any",
      };
   }
}
