import { AutoBeOpenApi, AutoBeProgressEventBase } from "@autobe/interface";
import { missedOpenApiSchemas } from "@autobe/utils";

import { AutoBeContext } from "../../context/AutoBeContext";
import { orchestrateInterfaceSchemaCasting } from "./orchestrateInterfaceSchemaCasting";
import { orchestrateInterfaceSchemaComplement } from "./orchestrateInterfaceSchemaComplement";
import { orchestrateInterfaceSchemaDecouple } from "./orchestrateInterfaceSchemaDecouple";
import { orchestrateInterfaceSchemaRefine } from "./orchestrateInterfaceSchemaRefine";
import { orchestrateInterfaceSchemaRename } from "./orchestrateInterfaceSchemaRename";
import { orchestrateInterfaceSchemaReview } from "./orchestrateInterfaceSchemaReview";
import { orchestrateInterfaceSchemaWrite } from "./orchestrateInterfaceSchemaWrite";
import { AutoBeInterfaceSchemaReviewProgrammer } from "./programmers/AutoBeInterfaceSchemaReviewProgrammer";
import { AutoBeJsonSchemaCollection } from "./utils/AutoBeJsonSchemaCollection";
import { AutoBeJsonSchemaFactory } from "./utils/AutoBeJsonSchemaFactory";
import { AutoBeJsonSchemaNamingConvention } from "./utils/AutoBeJsonSchemaNamingConvention";

export const orchestrateInterfaceSchema = async (
  ctx: AutoBeContext,
  props: {
    instruction: string;
    operations: AutoBeOpenApi.IOperation[];
  },
): Promise<Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>> => {
  //----
  // PREPARATIONS
  //----
  // MOCK DOCUMENT
  const document: AutoBeOpenApi.IDocument = {
    operations: props.operations,
    components: {
      authorizations: ctx.state().analyze?.actors ?? [],
      schemas: {},
    },
  };

  // RENAME REQUEST/RESPONSE BODY TYPE NAMES
  const renameProgress: AutoBeProgressEventBase = {
    completed: 0,
    total: 0,
  };
  AutoBeJsonSchemaNamingConvention.normalize({
    operations: document.operations,
    collection: new AutoBeJsonSchemaCollection({}, {}),
  });
  await orchestrateInterfaceSchemaRename(ctx, {
    operations: document.operations,
    progress: renameProgress,
    collection: new AutoBeJsonSchemaCollection({}, {}),
  });

  // PREPARE ITERATOR
  const castingProgress: AutoBeProgressEventBase = {
    completed: 0,
    total: 0,
  };
  const refineProgress: AutoBeProgressEventBase = {
    completed: 0,
    total: 0,
  };
  const reviewProgress: AutoBeProgressEventBase = {
    completed: 0,
    total: 0,
  };

  //----
  // LOGIC FUNCTION
  //----
  const iterate = async (
    initialize: () => Promise<Record<string, AutoBeOpenApi.IJsonSchema>>,
  ) => {
    const schemas: Record<string, AutoBeOpenApi.IJsonSchemaDescriptive> = {};
    const overwrite = async (
      next: Record<
        string,
        AutoBeOpenApi.IJsonSchema | AutoBeOpenApi.IJsonSchemaDescriptive
      >,
    ) => {
      for (const [k, v] of Object.entries(next))
        if (v === undefined) delete next[k];
      if (Object.keys(next).length === 0) return;

      // assign schemas
      const collection: AutoBeJsonSchemaCollection =
        new AutoBeJsonSchemaCollection(document.components.schemas, schemas);
      collection.assign(
        next as Record<string, AutoBeOpenApi.IJsonSchemaDescriptive>,
      );
      collection.assign(
        AutoBeJsonSchemaFactory.presets(new Set(Object.keys(schemas))),
      );

      // naming convention
      AutoBeJsonSchemaNamingConvention.normalize({
        operations: document.operations,
        collection,
      });
      await orchestrateInterfaceSchemaRename(ctx, {
        operations: document.operations,
        progress: renameProgress,
        collection,
      });

      // special logics
      AutoBeJsonSchemaFactory.fixPaginationSchemas(document.components.schemas);
      AutoBeJsonSchemaFactory.fixAuthorizationSchemas(
        document.components.schemas,
      );
      AutoBeJsonSchemaFactory.finalize({
        application: ctx.state().database!.result.data,
        operations: document.operations,
        collection,
      });
    };

    // initialize schemas
    await overwrite(await initialize());

    // type casting
    await overwrite(
      await orchestrateInterfaceSchemaCasting(ctx, {
        instruction: props.instruction,
        document: {
          operations: document.operations,
          components: {
            authorizations: ctx.state().analyze?.actors ?? [],
            schemas: document.components.schemas,
          },
        },
        schemas,
        progress: castingProgress,
      }),
    );

    // refine schemas
    await overwrite(
      await orchestrateInterfaceSchemaRefine(ctx, {
        instruction: props.instruction,
        document,
        schemas,
        progress: refineProgress,
      }),
    );

    // review schemas
    const REVIEW_ITERATIONS = 1;
    reviewProgress.total +=
      Object.entries(schemas).filter(([k, v]) =>
        AutoBeInterfaceSchemaReviewProgrammer.filter(k, v),
      ).length * REVIEW_ITERATIONS;
    for (let i: number = 0; i < REVIEW_ITERATIONS; i++)
      await overwrite(
        await orchestrateInterfaceSchemaReview(ctx, {
          instruction: props.instruction,
          document,
          schemas,
          progress: reviewProgress,
        }),
      );
  };

  //----
  // SCHEMA GENERATION LOOP
  //----
  // INITIAL SCHEMAS
  await iterate(() =>
    orchestrateInterfaceSchemaWrite(ctx, {
      instruction: props.instruction,
      operations: document.operations,
    }),
  );

  // COMPLEMENTATION
  const complementProgress: AutoBeProgressEventBase = {
    completed: 0,
    total: 0,
  };
  const failures: Map<string, number> = new Map();
  while (missedOpenApiSchemas(document).length !== 0)
    await iterate(() =>
      orchestrateInterfaceSchemaComplement(ctx, {
        instruction: props.instruction,
        progress: complementProgress,
        failures,
        document,
      }),
    );

  // DECOUPLE - break cross-type circular references (A->B->C->A)
  await orchestrateInterfaceSchemaDecouple(ctx, {
    schemas: document.components.schemas,
    operations: document.operations,
  });

  AutoBeJsonSchemaFactory.removeUnused({
    operations: document.operations,
    schemas: document.components.schemas,
  });
  return document.components.schemas;
};
