import { AutoBeOpenApi } from "@autobe/interface";
import { StringUtil } from "@autobe/utils";
import typia, { IValidation } from "typia";

import { AutoBeSystemPromptConstant } from "../../../constants/AutoBeSystemPromptConstant";

export const fulfillJsonSchemaErrorMessages = (
  errors: IValidation.IError[],
): void => {
  for (const e of errors)
    fulfillTypeAsArrayError(e) ||
      fulfillEnumInsteadOfConstError(e) ||
      fulfillNoRequiredError(e) ||
      fulfillObjectMetadataMisplacement(e) ||
      fulfillNestedObjectError(e) ||
      fulfillJsonSchemaFormat(e);
};

const fulfillTypeAsArrayError = (e: IValidation.IError): boolean => {
  if (
    // type := ["number", "string", ...] case
    isInvalidJsonSchema(e) &&
    typia.is<{ type: string[] }>(e.value) === true
  ) {
    e.description =
      AutoBeSystemPromptConstant.INTERFACE_SCHEMA_MISSING_TYPE_ARRAY.replace(
        "${{JSON}}",
        StringUtil.trim`
        {
          "oneOf": [
        ${e.value.type.map((t) => `    { "type": ${JSON.stringify(t)}, ... },`).join("\n")}
          ],${"description" in e.value ? `\n  "description": ${JSON.stringify(e.value.description)},` : ""}
        }
      `,
      );
    return true;
  }
  return false;
};

const fulfillEnumInsteadOfConstError = (e: IValidation.IError): boolean => {
  if (
    // enum to const
    isInvalidJsonSchema(e) &&
    // biome-ignore lint: intended
    typia.is<{ enum: any[] }>(e.value) === true
  ) {
    e.description = StringUtil.trim`
      **Invalid Schema: "enum" keyword is not supported in AutoBE.**

      The "enum" keyword is prohibited. AutoBE requires you to use the "oneOf"
      construct with individual "const" values instead. This design ensures
      better type safety and documentation clarity.

      Convert your schema to the following format:

      \`\`\`json
      {
        "oneOf": [
      ${e.value.enum.map((t) => `    { "const": ${JSON.stringify(t)} },`).join("\n")}
        ],${"description" in e.value ? `\n  "description": ${JSON.stringify(e.value.description)},` : ""}
      }
      \`\`\`

      You must make this correction. The validator will continue to reject your
      schema until you replace "enum" with the proper "oneOf" + "const" structure.
    `;
    return true;
  }
  return false;
};

const fulfillNoRequiredError = (e: IValidation.IError): boolean => {
  if (
    // no required property
    e.value === undefined &&
    e.path.endsWith(".required") &&
    e.expected === "Array<string>"
  ) {
    e.description =
      AutoBeSystemPromptConstant.INTERFACE_SCHEMA_MISSING_REQUIRED;
    return true;
  }
  return false;
};

const fulfillObjectMetadataMisplacement = (e: IValidation.IError): boolean => {
  if (isInvalidJsonSchema(e) === false) return false;

  const validate = (props: {
    key: string;
    expected: string;
    actual: string;
    place: string;
    purpose: string;
  }): boolean => {
    e.expected = "undefined";
    e.description = StringUtil.trim`
      **Structural Error: "${props.key}" is in the wrong location**

      You placed "${props.key}" inside the "properties" object, but it is a
      metadata field that belongs at the object type level, outside of "properties".

      - Your placement: \`${props.actual}\`
      - Correct placement: \`${props.expected}\`

      The "${props.key}" field describes ${props.purpose} and must be placed
      at the schema's top level alongside "type" and "properties".

      **Your current structure (incorrect)**:
      \`\`\`json
      {
        "type": "object",
        "properties": {
          ...,
          "${props.key}": ${JSON.stringify(e.value)}
        }
      }
      \`\`\`

      **Required structure**:
      \`\`\`json
      {
        "type": "object",
        "${props.key}": ${JSON.stringify(e.value)},
        "properties": { ... }
      }
      \`\`\`

      Move "${props.key}" from ${e.path} to ${props.place}. The validator will
      continue to reject your schema until this structural correction is made.
    `;
    return true;
  };

  if (
    e.path.endsWith(`.properties.required`) === true &&
    Array.isArray(e.value) === true
  )
    return validate({
      key: "required",
      expected: `AutoBeOpenApi.IJsonSchemaDescriptive.IObject.required`,
      actual: `AutoBeOpenApi.IJsonSchemaDescriptive.IObject.properties.required`,
      place: e.path.replace(`.properties.required`, `.required`),
      purpose: "which properties are mandatory",
    });
  return false;
};

const fulfillNestedObjectError = (e: IValidation.IError): boolean => {
  if (isExcludedObjectType(e) === true) {
    // nested object
    e.description =
      AutoBeSystemPromptConstant.INTERFACE_SCHEMA_MISSING_NESTED_OBJECT;
    return true;
  }
  return false;
};

const fulfillJsonSchemaFormat = (e: IValidation.IError): boolean => {
  const supported: string[] =
    typia.misc.literals<
      Required<AutoBeOpenApi.IJsonSchema.IString>["format"]
    >();
  if (
    e.path.endsWith(".format") &&
    typeof e.value === "string" &&
    supported.every((s) => e.expected.includes(JSON.stringify(s)))
  ) {
    e.expected = "undefined";
    e.description = StringUtil.trim`
      **Invalid "format" Value: ${JSON.stringify(e.value)} is not supported.**

      The "format" property value ${JSON.stringify(e.value)} is not supported in the
      JSOn schema specification (type: \`AutoBeOpenApi.IJsonSchema.IString.format\`).

      Supported values are:
      
      ${supported.map((s) => `- ${s}`).join("\n")}

      If your intended format does not exactly match one of the above values,
      you must remove the "format" property entirely from this schema.
      Do not attempt to use similar or alternative format strings.
      Simply delete the "format" property — it is optional.

      The validator will continue to reject your schema until the "format"
      property is either removed or set to an exactly matching supported value.
    `;
    return true;
  }
  return false;
};

const isExcludedObjectType = (error: IValidation.IError): boolean =>
  error.expected.includes("|") &&
  ((error.expected.includes("AutoBeOpenApi.IJsonSchema.IConstant") &&
    error.expected.includes("AutoBeOpenApi.IJsonSchema.IArray")) ||
    (error.expected.includes(
      "AutoBeOpenApi.IJsonSchemaDescriptive.IConstant",
    ) &&
      error.expected.includes("AutoBeOpenApi.IJsonSchemaDescriptive.IArray")) ||
    (error.expected.includes("AutoBeOpenApi.IJsonSchemaProperty.IConstant") &&
      error.expected.includes("AutoBeOpenApi.IJsonSchemaProperty.IArray"))) &&
  typia.is<{
    type: "object";
  }>(error.value) === true;

const isInvalidJsonSchema = (e: IValidation.IError): boolean =>
  e.expected.startsWith("(") &&
  e.expected.endsWith(")") &&
  e.expected.includes("|") &&
  e.expected
    .split("|")
    .map((s) => s.trim())
    .some(
      (s) =>
        s.startsWith("AutoBeOpenApi.IJsonSchema.") ||
        s.startsWith("AutoBeOpenApi.IJsonSchemaDescriptive."),
    );
