/*!
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import type { Node as FlowNodeType, Edge as FlowEdgeType } from "@xyflow/react";
import type { ElkExtendedEdge } from "elkjs";

import type { LightGridTaskInstanceSummary, NodeResponse } from "openapi/requests/types.gen";

import type { LayoutNode } from "./useGraphLayout";

export type CustomNodeProps = {
  assetCondition?: NodeResponse["asset_condition_type"];
  childCount?: number;
  depth?: number;
  height?: number;
  id: string;
  isFiltered?: boolean;
  isGroup?: boolean;
  isMapped?: boolean;
  isOpen?: boolean;
  isSelected?: boolean;
  label: string;
  operator?: string | null;
  setupTeardownType?: NodeResponse["setup_teardown_type"];
  taskInstance?: LightGridTaskInstanceSummary;
  team?: string | null;
  tooltip?: string | null;
  type: string;
  width?: number;
};

type NodeType = FlowNodeType<CustomNodeProps>;

type FlattenNodesProps = {
  children?: Array<LayoutNode>;
  level?: number;
  parent?: NodeType;
};

// Generate a flattened list of nodes for react-flow to render.
// Uses push() throughout to avoid the O(n²) spread-in-loop pattern that the
// previous implementation had (edges = [...edges, ...newEdges] per node).
export const flattenGraph = ({
  children,
  level = 0,
  parent,
}: FlattenNodesProps): {
  edges: Array<ElkExtendedEdge>;
  nodes: Array<NodeType>;
} => {
  const nodes: Array<NodeType> = [];
  const edges: Array<ElkExtendedEdge> = [];

  if (!children) {
    return { edges, nodes };
  }
  const parentNode = parent ? { parentNode: parent.id } : undefined;

  children.forEach((node) => {
    const x = (parent?.position.x ?? 0) + (node.x ?? 0);
    const y = (parent?.position.y ?? 0) + (node.y ?? 0);
    const newNode = {
      data: { ...node, depth: level },
      height: node.height,
      id: node.id,
      position: { x, y },
      type: node.type,
      width: node.width,
      ...parentNode,
    } satisfies NodeType;

    nodes.push(newNode);

    for (const edge of node.edges ?? []) {
      edges.push({
        ...edge,
        labels: edge.labels?.map((label) => ({
          ...label,
          x: (label.x ?? 0) + x,
          y: (label.y ?? 0) + y,
        })),
        sections: edge.sections?.map((section) => ({
          ...section,
          bendPoints: section.bendPoints?.map((bp) => ({ x: bp.x + x, y: bp.y + y })),
          endPoint: { x: section.endPoint.x + x, y: section.endPoint.y + y },
          startPoint: { x: section.startPoint.x + x, y: section.startPoint.y + y },
        })),
      });
    }

    if (node.children) {
      const { edges: childEdges, nodes: childNodes } = flattenGraph({
        children: node.children as Array<LayoutNode>,
        level: level + 1,
        parent: newNode,
      });

      nodes.push(...childNodes);
      edges.push(...childEdges);
    }
  });

  return { edges, nodes };
};

type Edge = {
  parentNode?: string;
} & ElkExtendedEdge;

export type EdgeData = {
  rest: {
    edgeType?: "data" | "scheduling";
    isFiltered?: boolean;
    isSelected?: boolean;
    isSetupTeardown?: boolean;
  } & ElkExtendedEdge;
};

export const formatFlowEdges = ({ edges }: { edges: Array<Edge> }): Array<FlowEdgeType<EdgeData>> =>
  edges.map((edge) => ({
    data: { rest: edge },
    id: edge.id,
    source: edge.sources[0] ?? "",
    target: edge.targets[0] ?? "",
    type: "custom",
  }));
