import { IAgenticaController } from "@agentica/core";
import {
  AutoBeAnalyze,
  AutoBeEventSource,
  AutoBeInterfaceAuthorization,
  AutoBeInterfaceAuthorizationEvent,
  AutoBeOpenApi,
  AutoBeProgressEventBase,
} from "@autobe/interface";
import { NamingConvention } from "@typia/utils";
import { IPointer, Singleton } from "tstl";
import typia, { ILlmApplication, IValidation } from "typia";
import { v7 } from "uuid";

import { AutoBeContext } from "../../context/AutoBeContext";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformInterfaceAuthorizationHistory } from "./histories/transformInterfaceAuthorizationHistory";
import { AutoBeInterfaceAuthorizationProgrammer } from "./programmers/AutoBeInterfaceAuthorizationProgrammer";
import { IAutoBeInterfaceAuthorizationApplication } from "./structures/IAutoBeInterfaceAuthorizationApplication";
import { AutoBeJsonSchemaFactory } from "./utils/AutoBeJsonSchemaFactory";

export async function orchestrateInterfaceAuthorization(
  ctx: AutoBeContext,
  props: {
    instruction: string;
  },
): Promise<AutoBeInterfaceAuthorization[]> {
  const actors: AutoBeAnalyze.IActor[] = ctx.state().analyze?.actors ?? [];
  const progress: AutoBeProgressEventBase = {
    total: actors.length,
    completed: 0,
  };
  return await executeCachedBatch(
    ctx,
    actors.map((a) => async (promptCacheKey) => {
      const counter = new Singleton(() => ++progress.completed);
      try {
        const event: AutoBeInterfaceAuthorizationEvent = await process(ctx, {
          actor: a,
          progress,
          counter,
          promptCacheKey,
          instruction: props.instruction,
        });
        ctx.dispatch(event);
        return {
          name: a.name,
          operations: event.operations,
        };
      } catch (error) {
        counter.get();
        throw error;
      }
    }),
  );
}

async function process(
  ctx: AutoBeContext,
  props: {
    instruction: string;
    actor: AutoBeAnalyze.IActor;
    progress: AutoBeProgressEventBase;
    counter: Singleton<number>;
    promptCacheKey: string;
  },
): Promise<AutoBeInterfaceAuthorizationEvent> {
  const prefix: string = NamingConvention.camel(ctx.state().analyze!.prefix);
  const preliminary: AutoBePreliminaryController<
    | "analysisSections"
    | "previousAnalysisSections"
    | "databaseSchemas"
    | "previousDatabaseSchemas"
    | "complete"
  > = new AutoBePreliminaryController({
    application:
      typia.json.application<IAutoBeInterfaceAuthorizationApplication>(),
    source: SOURCE,
    kinds: [
      "analysisSections",
      "previousAnalysisSections",
      "databaseSchemas",
      "previousDatabaseSchemas",
      "complete",
    ],
    state: ctx.state(),
    dispatch: (e) => ctx.dispatch(e),
  });
  return await preliminary.orchestrate(ctx, async (out) => {
    const pointer: IPointer<IAutoBeInterfaceAuthorizationApplication.IWrite | null> =
      {
        value: null,
      };
    const result: AutoBeContext.IResult = await ctx.conversate({
      source: SOURCE,
      controller: createController({
        actor: props.actor,
        build: (next) => {
          pointer.value = next;
        },
        preliminary,
        prefix,
      }),
      enforceFunctionCall: true,
      promptCacheKey: props.promptCacheKey,
      ...transformInterfaceAuthorizationHistory({
        state: ctx.state(),
        prefix,
        instruction: props.instruction,
        actor: props.actor,
        preliminary,
      }),
    });
    if (pointer.value === null) return out(result)(null);

    const operations: AutoBeOpenApi.IOperation[] =
      AutoBeInterfaceAuthorizationProgrammer.fixOperations({
        operations: pointer.value?.operations ?? [],
        prefix,
      });
    return out(result)({
      type: SOURCE,
      id: v7(),
      analysis: pointer.value.analysis,
      rationale: pointer.value.rationale,
      operations,
      acquisition: preliminary.getAcquisition(),
      metric: result.metric,
      tokenUsage: result.tokenUsage,
      created_at: new Date().toISOString(),
      step: ctx.state().analyze?.step ?? 0,
      total: props.progress.total,
      completed: props.counter.get(),
    } satisfies AutoBeInterfaceAuthorizationEvent);
  });
}

function createController(props: {
  prefix: string | null;
  actor: AutoBeAnalyze.IActor;
  preliminary: AutoBePreliminaryController<
    | "analysisSections"
    | "previousAnalysisSections"
    | "databaseSchemas"
    | "previousDatabaseSchemas"
    | "complete"
  >;
  build: (next: IAutoBeInterfaceAuthorizationApplication.IWrite) => void;
}): IAgenticaController.IClass {
  const validate = (
    next: unknown,
  ): IValidation<IAutoBeInterfaceAuthorizationApplication.IProps> => {
    const result: IValidation<IAutoBeInterfaceAuthorizationApplication.IProps> =
      typia.validate<IAutoBeInterfaceAuthorizationApplication.IProps>(next);
    if (result.success === false) return result;
    else if (result.data.request.type !== "write")
      return props.preliminary.validate({
        thinking: result.data.thinking,
        request: result.data.request,
      });

    const errors: IValidation.IError[] = [];
    AutoBeInterfaceAuthorizationProgrammer.validateAuthorizationTypes({
      errors,
      actor: props.actor,
      operations: result.data.request.operations,
      accessor: "$input.request.operations",
    });
    result.data.request.operations.forEach((operation, index) =>
      AutoBeInterfaceAuthorizationProgrammer.validateOperation({
        errors,
        prefix: props.prefix,
        actor: props.actor,
        operation,
        accessor: `$input.request.operations[${index}]`,
      }),
    );
    if (errors.length !== 0) {
      return {
        success: false,
        errors,
        data: next,
      };
    }
    return result;
  };

  const application: ILlmApplication = props.preliminary.fixApplication(
    typia.llm.application<IAutoBeInterfaceAuthorizationApplication>({
      validate: {
        process: validate,
      },
    }),
  );
  return {
    protocol: "class",
    name: SOURCE,
    application,
    execute: {
      process: (next) => {
        if (next.request.type === "write") {
          for (const o of next.request.operations)
            for (const p of o.parameters)
              AutoBeJsonSchemaFactory.fixSchema(p.schema);
          next.request.operations = next.request.operations.filter(
            (operation) =>
              AutoBeInterfaceAuthorizationProgrammer.filter({
                actor: props.actor.kind,
                operation,
              }),
          );
          props.build(next.request);
        }
      },
    } satisfies IAutoBeInterfaceAuthorizationApplication,
  };
}

const SOURCE = "interfaceAuthorization" satisfies AutoBeEventSource;
