import {
  AutoBeDatabase,
  AutoBeOpenApi,
  AutoBeProgressEventBase,
  AutoBeRealizeTransformerFunction,
} from "@autobe/interface";
import { AutoBeOpenApiTypeChecker } from "@autobe/utils";
import typia, { ILlmApplication, IValidation } from "typia";

import { AutoBeContext } from "../../context/AutoBeContext";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { orchestrateRealizeCorrectOverall } from "./correct/orchestrateRealizeCorrectOverall";
import { transformRealizeTransformerCorrectHistory } from "./histories/transformRealizeTransformerCorrectHistory";
import { AutoBeRealizeTransformerProgrammer } from "./programmers/AutoBeRealizeTransformerProgrammer";
import { IAutoBeRealizeFunctionResult } from "./structures/IAutoBeRealizeFunctionResult";
import { IAutoBeRealizeTransformerCorrectApplication } from "./structures/IAutoBeRealizeTransformerCorrectApplication";

export const orchestrateRealizeTransformerCorrectOverall = async (
  ctx: AutoBeContext,
  props: {
    functions: AutoBeRealizeTransformerFunction[];
    progress: AutoBeProgressEventBase;
  },
): Promise<
  IAutoBeRealizeFunctionResult<AutoBeRealizeTransformerFunction>[]
> => {
  const prismaApplication: AutoBeDatabase.IApplication =
    ctx.state().database!.result.data;
  const document: AutoBeOpenApi.IDocument = ctx.state().interface!.document;
  const getNeighbors = (
    func: AutoBeRealizeTransformerFunction,
  ): AutoBeRealizeTransformerFunction[] => {
    const visited: Set<string> = new Set();
    AutoBeOpenApiTypeChecker.visit({
      components: document.components,
      schema: { $ref: `#/components/schemas/${func.plan.dtoTypeName}` },
      closure: (next) => {
        if (AutoBeOpenApiTypeChecker.isReference(next)) {
          const key: string = next.$ref.split("/").pop()!;
          visited.add(key);
        }
      },
    });
    return props.functions.filter(
      (y) =>
        y.plan.dtoTypeName !== func.plan.dtoTypeName &&
        visited.has(y.plan.dtoTypeName),
    );
  };
  return await orchestrateRealizeCorrectOverall(ctx, {
    programmer: {
      location: "src/transformers",

      // Recalculate template for corrected transformer function
      template: (func) => {
        const model: AutoBeDatabase.IModel = prismaApplication.files
          .map((f) => f.models)
          .flat()
          .find((m) => m.name === func.plan.databaseSchemaName)!;
        return AutoBeRealizeTransformerProgrammer.writeTemplate({
          plan: func.plan,
          schema: document.components.schemas[
            func.plan.dtoTypeName
          ] as AutoBeOpenApi.IJsonSchemaDescriptive.IObject,
          schemas: document.components.schemas,
          neighbors: getNeighbors(func).map((n) => n.plan),
          relations: AutoBeRealizeTransformerProgrammer.getRelationMappingTable(
            {
              application: prismaApplication,
              model,
            },
          ),
          model,
        });
      },

      // Replace import statements using Transformer-specific programmer
      replaceImportStatements: async (next) => {
        return await AutoBeRealizeTransformerProgrammer.replaceImportStatements(
          ctx,
          {
            dtoTypeName: next.function.plan.dtoTypeName,
            schemas: document.components.schemas,
            code: next.code,
          },
        );
      },

      // No additional files needed for transformers (unlike operations)
      additional: (_functions) => ({}),

      // Create preliminary controller with only databaseSchemas support
      preliminary: (next) =>
        new AutoBePreliminaryController<"databaseSchemas">({
          source: next.source,
          application:
            typia.json.application<IAutoBeRealizeTransformerCorrectApplication>(),
          kinds: ["databaseSchemas"],
          dispatch: (e) => ctx.dispatch(e),
          state: ctx.state(),
          local: {
            databaseSchemas: ctx
              .state()
              .database!.result.data.files.map((f) => f.models)
              .flat()
              .filter((m) => m.name === next.function.plan.databaseSchemaName),
          },
        }),

      // Transform history using Transformer-specific transformer
      histories: (next) =>
        transformRealizeTransformerCorrectHistory(ctx, {
          function: next.function,
          neighbors: getNeighbors(next.function),
          failures: next.failures,
          preliminary: next.preliminary,
        }),

      // Create controller with Transformer-specific validation
      controller: (next) => {
        const validate: Validator = (input) => {
          const result: IValidation<IAutoBeRealizeTransformerCorrectApplication.IProps> =
            typia.validate<IAutoBeRealizeTransformerCorrectApplication.IProps>(
              input,
            );
          if (result.success === false) return result;
          else if (result.data.request.type !== "write")
            return next.preliminary.validate({
              thinking: result.data.thinking,
              request: result.data.request,
            });

          // Validate transformer-specific constraints
          const errors: IValidation.IError[] =
            AutoBeRealizeTransformerProgrammer.validate({
              application: prismaApplication,
              document,
              plan: next.function.plan,
              neighbors: props.functions.map((f) => f.plan),
              transformMappings: result.data.request.transformMappings,
              selectMappings: result.data.request.selectMappings,
              draft: result.data.request.draft,
              revise: result.data.request.revise,
            });
          return errors.length
            ? {
                success: false,
                errors,
                data: result.data,
              }
            : result;
        };

        const application: ILlmApplication = next.preliminary.fixApplication(
          typia.llm.application<IAutoBeRealizeTransformerCorrectApplication>({
            validate: {
              process: validate,
            },
          }),
        );
        AutoBeRealizeTransformerProgrammer.fixApplication({
          definition: application,
          application: prismaApplication,
          document,
          plan: next.function.plan,
        });

        return {
          protocol: "class",
          name: next.source,
          application,
          execute: {
            process: (v) => {
              if (v.request.type === "write") next.build(v.request);
            },
          } satisfies IAutoBeRealizeTransformerCorrectApplication,
        };
      },
    },
    functions: props.functions,
    progress: props.progress,
  });
};

type Validator = (
  input: unknown,
) => IValidation<IAutoBeRealizeTransformerCorrectApplication.IProps>;
