import {
  AutoBeDatabase,
  AutoBeOpenApi,
  AutoBePreliminaryKind,
} from "@autobe/interface";
import { AutoBeOpenApiEndpointComparator, StringUtil } from "@autobe/utils";
import { LlmTypeChecker } from "@typia/utils";
import typia, { ILlmApplication, ILlmFunction, ILlmSchema } from "typia";

import { AutoBeState } from "../../../context/AutoBeState";
import { AutoBePreliminaryController } from "../AutoBePreliminaryController";
import { IAutoBePreliminaryRequest } from "../structures/AutoBePreliminaryRequest";
import { IAnalysisSectionEntry } from "../structures/IAnalysisSectionEntry";
import { IAutoBePreliminaryGetAnalysisSections } from "../structures/IAutoBePreliminaryGetAnalysisSections";
import { IAutoBePreliminaryGetDatabaseSchemas } from "../structures/IAutoBePreliminaryGetDatabaseSchemas";
import { IAutoBePreliminaryGetInterfaceOperations } from "../structures/IAutoBePreliminaryGetInterfaceOperations";
import { IAutoBePreliminaryGetInterfaceSchemas } from "../structures/IAutoBePreliminaryGetInterfaceSchemas";
import { IAutoBePreliminaryGetPreviousAnalysisSections } from "../structures/IAutoBePreliminaryGetPreviousAnalysisSections";
import { IAutoBePreliminaryGetPreviousDatabaseSchemas } from "../structures/IAutoBePreliminaryGetPreviousDatabaseSchemas";
import { IAutoBePreliminaryGetPreviousInterfaceOperations } from "../structures/IAutoBePreliminaryGetPreviousInterfaceOperations";
import { IAutoBePreliminaryGetPreviousInterfaceSchemas } from "../structures/IAutoBePreliminaryGetPreviousInterfaceSchemas";
import { IAutoBePreliminaryGetRealizeCollectors } from "../structures/IAutoBePreliminaryGetRealizeCollectors";
import { IAutoBePreliminaryGetRealizeTransformers } from "../structures/IAutoBePreliminaryGetRealizeTransformers";

export const fixPreliminaryApplication = <
  Kind extends AutoBePreliminaryKind,
>(props: {
  state: AutoBeState;
  preliminary: AutoBePreliminaryController<Kind>;
  application: ILlmApplication;
  enumerable: boolean;
}): void => {
  if (
    props.preliminary.getKinds().some((k) => k.includes("previous")) === false
  )
    return;

  const func: ILlmFunction | undefined = props.application.functions.find(
    (f) => f.name === "process",
  );
  if (func === undefined) return;

  const request: ILlmSchema | undefined = func.parameters.properties.request;
  if (request === undefined) return;

  const eraseKind = (kind: AutoBePreliminaryKind) => {
    props.preliminary.getKinds().splice(
      props.preliminary.getKinds().indexOf(
        // biome-ignore lint: intended
        kind as any,
      ),
      1,
    );
    // biome-ignore lint: intended
    delete (props.preliminary.getAll() as any)[kind];
    // biome-ignore lint: intended
    delete (props.preliminary.getLocal() as any)[kind];
  };
  const eraseMetadata = getUnionErasure({
    $defs: func.parameters.$defs,
    request,
  });
  if (eraseMetadata === null) return;

  for (const kind of props.preliminary.getKinds().slice())
    if (kind === "previousAnalysisSections") {
      if (props.state.previousAnalyze === null) {
        eraseMetadata("getPreviousAnalysisSections");
        eraseKind(kind);
      }
    } else if (kind === "previousDatabaseSchemas") {
      if (props.state.previousDatabase === null) {
        eraseMetadata("getPreviousDatabaseSchemas");
        eraseKind(kind);
      }
    } else if (kind === "previousInterfaceOperations") {
      if (props.state.previousInterface === null) {
        eraseMetadata("getPreviousInterfaceOperations");
        eraseKind(kind);
      }
    } else if (kind === "previousInterfaceSchemas") {
      if (props.state.previousInterface === null) {
        eraseMetadata("getPreviousInterfaceSchemas");
        eraseKind(kind);
      }
    }

  for (const kind of props.preliminary.getKinds()) {
    const accessor: Exclude<AutoBePreliminaryKind, `previous${string}`> = (
      kind.startsWith("previous")
        ? (() => {
            const value = kind.replace("previous", "");
            return value[0].toLowerCase() + value.substring(1);
          })()
        : kind
    ) as Exclude<AutoBePreliminaryKind, `previous${string}`>;
    if (
      props.enumerable === false &&
      accessor !== "analysisSections" &&
      accessor !== "databaseSchemas"
    )
      continue;
    ApplicationFixer[accessor]({
      $defs: func.parameters.$defs,
      // biome-ignore lint: intended
      controller: props.preliminary as any,
      previous: kind.startsWith("previous"),
    });
  }
};

const getUnionErasure = (props: {
  $defs: Record<string, ILlmSchema>;
  request: ILlmSchema;
}) => {
  if (LlmTypeChecker.isAnyOf(props.request) === false) return null;
  else if (
    props.request.anyOf.some((s) => LlmTypeChecker.isReference(s) === false)
  )
    return null;

  const children: ILlmSchema.IReference[] = props.request
    .anyOf as ILlmSchema.IReference[];
  const mapping: Record<string, string> =
    props.request["x-discriminator"]?.mapping ?? {};

  return (
    key: IAutoBePreliminaryRequest<
      Extract<AutoBePreliminaryKind, `previous${string}`>
    >["request"]["type"],
  ): void => {
    const type: string = `IAutoBePreliminary${key[0].toUpperCase()}${key.substring(1)}`;
    const index: number = children.findIndex((c) =>
      c.$ref.endsWith(`/${type}`),
    );
    if (index !== -1) children.splice(index, 1);
    delete props.$defs[key];
    delete mapping[key];
  };
};

namespace ApplicationFixer {
  export const analysisSections = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<
      "analysisSections" | "previousAnalysisSections"
    >;
    previous: boolean;
  }): void => {
    const sections: IAnalysisSectionEntry[] =
      props.controller.getAll()[
        props.previous ? "previousAnalysisSections" : "analysisSections"
      ];
    if (sections.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      props.previous
        ? typia.reflect.name<IAutoBePreliminaryGetPreviousAnalysisSections>()
        : typia.reflect.name<IAutoBePreliminaryGetAnalysisSections>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;

    const array: ILlmSchema | undefined = type.properties.sectionIds;
    if (array === undefined) return;
    else if (LlmTypeChecker.isArray(array) === false) return;
    // describe(
    //   array,
    //   StringUtil.trim`
    //     Here is the catalog of analysis sections available for retrieval:

    //     ID | File | Unit | Section | Keywords
    //     ---|------|------|---------|----------
    //     ${sections
    //       .map(
    //         (s) =>
    //           `${s.id} | ${s.filename} | ${s.unitTitle} | ${s.sectionTitle} | ${s.keywords.join(", ")}`,
    //       )
    //       .join("\n")}
    //   `,
    // );

    const items: ILlmSchema = array.items;
    if (LlmTypeChecker.isInteger(items) === false) return;

    items.minimum = Math.min(...sections.map((s) => s.id));
    items.maximum = Math.max(...sections.map((s) => s.id));
  };

  export const databaseSchemas = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<
      "databaseSchemas" | "previousDatabaseSchemas"
    >;
    previous: boolean;
  }): void => {
    const schemas: AutoBeDatabase.IModel[] =
      props.controller.getAll()[
        props.previous ? "previousDatabaseSchemas" : "databaseSchemas"
      ];
    if (schemas.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      props.previous
        ? typia.reflect.name<IAutoBePreliminaryGetPreviousDatabaseSchemas>()
        : typia.reflect.name<IAutoBePreliminaryGetDatabaseSchemas>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;
    describe(
      type.properties.schemaNames,
      StringUtil.trim`
        Here is the list of database schemas available for retrieval:

        ${schemas
          .slice()
          .sort((a, b) => a.name.localeCompare(b.name))
          .map((s) => `- ${s.name}`)
          .join("\n")}
      `,
    );
  };

  export const interfaceOperations = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<
      "interfaceOperations" | "previousInterfaceOperations"
    >;
    previous: boolean;
  }): void => {
    const operations: AutoBeOpenApi.IOperation[] =
      props.controller.getAll()[
        props.previous ? "previousInterfaceOperations" : "interfaceOperations"
      ];
    if (operations.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      props.previous
        ? typia.reflect.name<IAutoBePreliminaryGetPreviousInterfaceOperations>()
        : typia.reflect.name<IAutoBePreliminaryGetInterfaceOperations>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;
    describe(
      type.properties.endpoints,
      StringUtil.trim`
        Here is the list of interface operations available for retrieval:

        path | method
        ---- | ------
        ${operations
          .slice()
          .sort(AutoBeOpenApiEndpointComparator.compare)
          .map((o) => ` ${o.path} | ${o.method}`)
          .join("\n")}
      `,
    );
  };

  export const interfaceSchemas = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<
      "interfaceSchemas" | "previousInterfaceSchemas"
    >;
    previous: boolean;
  }): void => {
    const dtoTypeNames: string[] = Object.keys(
      props.controller.getAll()[
        props.previous ? "previousInterfaceSchemas" : "interfaceSchemas"
      ] satisfies Record<string, AutoBeOpenApi.IJsonSchema>,
    );
    if (dtoTypeNames.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      props.previous
        ? typia.reflect.name<IAutoBePreliminaryGetPreviousInterfaceSchemas>()
        : typia.reflect.name<IAutoBePreliminaryGetInterfaceSchemas>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;
    describe(
      type.properties.typeNames,
      StringUtil.trim`
        Here is the list of interface schemas available for retrieval:

        ${dtoTypeNames
          .slice()
          .sort((a, b) => a.localeCompare(b))
          .map((name) => `- ${name}`)
          .join("\n")}
      `,
    );
  };

  export const realizeCollectors = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<"realizeCollectors">;
  }): void => {
    if (props.controller.getAll().realizeCollectors.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      typia.reflect.name<IAutoBePreliminaryGetRealizeCollectors>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;
    describe(
      type.properties.dtoTypeNames,
      StringUtil.trim`
        Here is the list of DTO types available for realize collectors:

        ${props.controller
          .getAll()
          .realizeCollectors.slice()
          .sort((a, b) => a.plan.dtoTypeName.localeCompare(b.plan.dtoTypeName))
          .map((x) => `- ${x.plan.dtoTypeName}`)
          .join("\n")}
      `,
    );
  };

  // biome-ignore lint: no-op for control signal kind
  export const complete = (_props: any): void => {};

  export const realizeTransformers = (props: {
    $defs: Record<string, ILlmSchema>;
    controller: AutoBePreliminaryController<"realizeTransformers">;
  }): void => {
    if (props.controller.getAll().realizeTransformers.length === 0) return;

    const type: ILlmSchema.IObject = props.$defs[
      typia.reflect.name<IAutoBePreliminaryGetRealizeTransformers>()
    ] as ILlmSchema.IObject;
    if (type === undefined) return;
    describe(
      type.properties.dtoTypeNames,
      StringUtil.trim`
        Here is the list of DTO types available for realize transformers:

        ${props.controller
          .getAll()
          .realizeTransformers.slice()
          .sort((a, b) => a.plan.dtoTypeName.localeCompare(b.plan.dtoTypeName))
          .map((x) => `- ${x.plan.dtoTypeName}`)
          .join("\n")}
      `,
    );
  };
}

const describe = (schema: ILlmSchema, content: string): void => {
  schema.description ??= "";
  if (schema.description.length > 0) schema.description += "\n\n";
  schema.description += content;
};
