import { z } from "zod";
import { controllerProcedure } from "../trpc.js";

type GraphNodeLike = {
  id: string;
  domain: string;
};

type GraphLinkLike = {
  source: string;
  target: string;
};

type ResourceGraphSnapshot = {
  nodes: GraphNodeLike[];
  links: GraphLinkLike[];
  meta: {
    resourceEid: string;
    resourceName: string;
    [key: string]: unknown;
  };
};

type ProjectGraphSnapshot = {
  nodes: GraphNodeLike[];
  links: GraphLinkLike[];
  meta: {
    projectCode: string;
    projectName: string;
    [key: string]: unknown;
  };
};

function filterGraphData<
  TNode extends GraphNodeLike,
  TLink extends GraphLinkLike,
>(input: {
  nodes: TNode[];
  links: TLink[];
  domain?: string;
  includeLinks?: boolean;
}) {
  const requestedDomain = input.domain?.trim().toUpperCase();
  const nodes = requestedDomain
    ? input.nodes.filter((node) => node.domain === requestedDomain)
    : input.nodes;
  const selectedNodeIds = new Set(nodes.map((node) => node.id));
  const links = input.includeLinks
    ? input.links.filter((link) => selectedNodeIds.has(link.source) && selectedNodeIds.has(link.target))
    : [];

  return {
    requestedDomain: requestedDomain ?? null,
    includedLinks: input.includeLinks ?? false,
    selectedNodeCount: nodes.length,
    selectedLinkCount: links.length,
    nodes,
    ...(input.includeLinks ? { links } : {}),
  };
}

function getAvailableDomains<TNode extends { domain: string }>(nodes: TNode[]) {
  return [...new Set(nodes.map((node) => node.domain))];
}

function formatResourceGraphDetail(input: {
  resourceId: string;
  graph: ResourceGraphSnapshot;
  domain?: string;
  includeLinks?: boolean;
}) {
  return {
    resource: {
      id: input.resourceId,
      eid: input.graph.meta.resourceEid,
      displayName: input.graph.meta.resourceName,
    },
    availableDomains: getAvailableDomains(input.graph.nodes),
    totalNodeCount: input.graph.nodes.length,
    totalLinkCount: input.graph.links.length,
    ...filterGraphData({
      nodes: input.graph.nodes,
      links: input.graph.links,
      ...(input.domain ? { domain: input.domain } : {}),
      ...(input.includeLinks !== undefined ? { includeLinks: input.includeLinks } : {}),
    }),
    meta: input.graph.meta,
  };
}

function formatProjectGraphDetail(input: {
  projectId: string;
  graph: ProjectGraphSnapshot;
  domain?: string;
  includeLinks?: boolean;
}) {
  return {
    project: {
      id: input.projectId,
      shortCode: input.graph.meta.projectCode,
      name: input.graph.meta.projectName,
    },
    availableDomains: getAvailableDomains(input.graph.nodes),
    totalNodeCount: input.graph.nodes.length,
    totalLinkCount: input.graph.links.length,
    ...filterGraphData({
      nodes: input.graph.nodes,
      links: input.graph.links,
      ...(input.domain ? { domain: input.domain } : {}),
      ...(input.includeLinks !== undefined ? { includeLinks: input.includeLinks } : {}),
    }),
    meta: input.graph.meta,
  };
}

export function createComputationGraphDetailProcedures(input: {
  resourceGraphInputSchema: z.AnyZodObject;
  projectGraphInputSchema: z.AnyZodObject;
  readResourceGraphSnapshot: (
    ctx: any,
    input: any,
  ) => Promise<ResourceGraphSnapshot>;
  readProjectGraphSnapshot: (
    ctx: any,
    input: any,
  ) => Promise<ProjectGraphSnapshot>;
}) {
  const resourceGraphDetailInputSchema = input.resourceGraphInputSchema.extend({
    domain: z.string().trim().min(1).optional(),
    includeLinks: z.boolean().optional(),
  });

  const projectGraphDetailInputSchema = input.projectGraphInputSchema.extend({
    domain: z.string().trim().min(1).optional(),
    includeLinks: z.boolean().optional(),
  });

  return {
    getResourceDataDetail: controllerProcedure
      .input(resourceGraphDetailInputSchema)
      .query(async ({ ctx, input: procedureInput }) => {
        const graph = await input.readResourceGraphSnapshot(ctx, procedureInput);
        return formatResourceGraphDetail({
          resourceId: procedureInput.resourceId,
          graph,
          ...(typeof procedureInput.domain === "string" ? { domain: procedureInput.domain } : {}),
          ...(typeof procedureInput.includeLinks === "boolean"
            ? { includeLinks: procedureInput.includeLinks }
            : {}),
        });
      }),

    getProjectDataDetail: controllerProcedure
      .input(projectGraphDetailInputSchema)
      .query(async ({ ctx, input: procedureInput }) => {
        const graph = await input.readProjectGraphSnapshot(ctx, procedureInput);
        return formatProjectGraphDetail({
          projectId: procedureInput.projectId,
          graph,
          ...(typeof procedureInput.domain === "string" ? { domain: procedureInput.domain } : {}),
          ...(typeof procedureInput.includeLinks === "boolean"
            ? { includeLinks: procedureInput.includeLinks }
            : {}),
        });
      }),
  };
}
