import { TrainTask } from '@/pages/Dataset/config';
import { changePropertyName, fittingString } from '@/utils';
import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6';
// @ts-ignore
import Hierarchy from '@antv/hierarchy';

export const nodeWidth = 90;
export const nodeHeight = 40;
export const vGap = nodeHeight + 20;
export const hGap = nodeHeight + 20;
export const ellipseWidth = nodeWidth;
export const labelPadding = 30;
export const nodeFontSize = 8;
export const datasetHGap = 20;

// 数据集节点
const datasetNodes: NodeConfig[] = [];

export enum NodeType {
  Current = 'Current', // 当前模型
  Parent = 'Parent', // 父模型
  Children = 'Children', // 子模型
  Project = 'Project', // 项目
  TrainDataset = 'TrainDataset', // 训练数据集
  TestDataset = 'TestDataset', // 测试数据集
}

export type Rect = {
  x: number; // 矩形中心的 x 坐标
  y: number; // 矩形中心的 y 坐标
  width: number;
  height: number;
};

export interface TrainDataset extends NodeConfig {
  repo_id: string;
  name: string;
  version: string;
  identifier: string;
  owner: string;
  model_type: NodeType.TestDataset | NodeType.TrainDataset;
}

export interface ProjectDependency extends NodeConfig {
  url: string;
  name: string;
  branch: string;
  model_type: NodeType.Project;
}

export type ModelMeta = {
  train_datasets?: TrainDataset[];
  test_datasets?: TrainDataset[];
  project_depency?: ProjectDependency;
  train_task?: TrainTask;
  name: string;
  version: string;
  model_source: string;
  model_type: string;
  create_time: string;
  file_size: string;
  is_public: boolean;
};

export interface ModelDepsAPIData {
  repo_id: number;
  model_name: string;
  version: string;
  workflow_id: number;
  exp_ins_id: number;
  model_type: NodeType.Children | NodeType.Current | NodeType.Parent;
  model_meta: ModelMeta;
  child_model_list: ModelDepsAPIData[];
  parent_model_vo?: ModelDepsAPIData;
}

export interface ModelDepsData extends Omit<ModelDepsAPIData, 'child_model_list'>, TreeGraphData {
  children: ModelDepsData[];
  expanded: boolean; // 是否展开
  level: number; // 层级，从 0 开始
  datasetLen: number; // 数据集数量
}

// 规范化子数据
export function normalizeChildren(data: ModelDepsData[]) {
  if (Array.isArray(data)) {
    data.forEach((item) => {
      item.model_type = NodeType.Children;
      item.expanded = false;
      item.level = 0;
      item.datasetLen = getDatasetLen(
        item.model_meta.train_datasets,
        item.model_meta.test_datasets,
      );
      item.id = `$M_${item.repo_id}_${item.version}`;
      item.label = getLabel(item);
      item.style = getStyle(NodeType.Children);
      normalizeChildren(item.children);
    });
  }
}

// 获取 label
export function getLabel(node: ModelDepsData | ModelDepsAPIData) {
  return (
    fittingString(`${node.model_name ?? ''}`, nodeWidth - labelPadding, nodeFontSize) +
    '\n' +
    fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize)
  );
}

// 获取数据集数量
export function getDatasetLen(train?: TrainDataset[], test?: TrainDataset[]) {
  return (train?.length || 0) + (test?.length || 0);
}

// 获取 style
export function getStyle(model_type: NodeType) {
  let fill = '';
  switch (model_type) {
    case NodeType.Current:
      fill = 'l(0) 0:#72a1ff 1:#1664ff';
      break;
    case NodeType.Parent:
      fill = 'l(0) 0:#93dfd1 1:#43c9b1';
      break;
    case NodeType.Children:
      fill = 'l(0) 0:#72b4ff 1:#169aff';
      break;
    case NodeType.Project:
      fill = 'l(0) 0:#b3a9ff 1:#8981ff';
      break;
    case NodeType.TrainDataset:
      fill = '#a5d878';
      break;
    case NodeType.TestDataset:
      fill = '#d8b578';
      break;
    default:
      break;
  }
  return {
    fill,
  };
}

// 将后台返回的数据转换成树形数据
export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData {
  // 将 children_models 转换成 children
  let normalizedData = changePropertyName(apiData, {
    child_model_list: 'children',
  }) as ModelDepsData;

  // 设置当前模型的数据
  normalizedData.model_type = NodeType.Current;
  normalizedData.id = `$M_${normalizedData.repo_id}_${normalizedData.version}`;
  normalizedData.label = getLabel(normalizedData);
  normalizedData.style = getStyle(NodeType.Current);
  normalizedData.expanded = true;
  normalizedData.datasetLen = getDatasetLen(
    normalizedData.model_meta.train_datasets,
    normalizedData.model_meta.test_datasets,
  );
  normalizeChildren(normalizedData.children as ModelDepsData[]);
  normalizedData.level = 0;

  // 将 parent_models 转换成树形结构
  let parent_model = normalizedData.parent_model_vo;
  while (parent_model) {
    const parent = parent_model;
    normalizedData = {
      ...parent,
      expanded: false,
      level: 0,
      datasetLen: getDatasetLen(parent.model_meta.train_datasets, parent.model_meta.test_datasets),
      model_type: NodeType.Parent,
      id: `$M_${parent.repo_id}_${parent.version}`,
      label: getLabel(parent),
      style: getStyle(NodeType.Parent),
      children: [
        {
          ...normalizedData,
          parent_model: null,
        },
      ],
    };
    parent_model = normalizedData.parent_model_vo;
  }
  return normalizedData;
}

// 将树形数据，使用 Hierarchy 进行布局，计算出坐标，然后转换成 G6 的数据
export function getGraphData(data: ModelDepsData, hierarchyNodes: ModelDepsData[]): GraphData {
  const config = {
    direction: 'LR',
    getHeight: () => nodeHeight,
    getWidth: () => nodeWidth,
    getVGap: (node: NodeConfig) => {
      const model = node as ModelDepsData;
      const { model_type, expanded, model_meta } = model;
      const { project_depency } = model_meta;
      if (model_type === NodeType.Current || model_type === NodeType.Parent) {
        return vGap / 2;
      }
      const selfGap = expanded && project_depency?.url ? nodeHeight + vGap : 0;
      const nextNode = getSameHierarchyNextNode(model, hierarchyNodes);
      if (!nextNode) {
        return vGap / 2;
      }
      const nextGap = nextNode.expanded === true && nextNode.datasetLen > 0 ? nodeHeight + vGap : 0;
      return (selfGap + nextGap + vGap) / 2;
    },
    getHGap: (node: NodeConfig) => {
      const model = node as ModelDepsData;
      return (
        (getHierarchyWidth(model.level, hierarchyNodes) +
          getHierarchyWidth(model.level + 1, hierarchyNodes) +
          hGap) /
        2
      );
    },
  };

  // 树形布局计算出坐标
  const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config);

  const nodes: NodeConfig[] = [];
  const edges: EdgeConfig[] = [];
  Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => {
    const data = node.data as ModelDepsData;
    // 当前模型显示数据集和项目
    if (data.expanded === true) {
      addDatasetDependency(data, node, nodes, edges);
      addProjectDependency(data, node, nodes, edges);
    } else if (data.model_type === NodeType.Children) {
      // adjustDatasetPosition(node);
    }
    nodes.push({
      ...data,
      x: node.x,
      y: node.y,
    });
    if (parent) {
      edges.push({
        source: parent.id,
        target: node.id,
      });
    }
  });
  return { nodes, edges };
}

// 将数据集转换成 G6 的数据
const addDatasetDependency = (
  data: ModelDepsData,
  currentNode: NodeConfig,
  nodes: NodeConfig[],
  edges: EdgeConfig[],
) => {
  const { repo_id, model_meta } = data;
  const { train_datasets, test_datasets } = model_meta;
  train_datasets?.forEach((item) => {
    if (!item.repo_id) {
      item.repo_id = item.id;
    }
    item.id = `$DTrain_${repo_id}_${item.repo_id}_${item.version}`;
    item.model_type = NodeType.TrainDataset;
    item.style = getStyle(NodeType.TrainDataset);
  });
  test_datasets?.forEach((item) => {
    if (!item.repo_id) {
      item.repo_id = item.id;
    }
    item.id = `$DTest_${repo_id}_${item.repo_id}_${item.version}`;
    item.model_type = NodeType.TestDataset;
    item.style = getStyle(NodeType.TestDataset);
  });

  datasetNodes.length = 0;
  const len = getDatasetLen(train_datasets, test_datasets);
  [...(train_datasets ?? []), ...(test_datasets ?? [])].forEach((item, index) => {
    const node = { ...item };
    node.type = 'ellipse';
    node.size = [ellipseWidth, nodeHeight];
    node.label =
      fittingString(node.name, ellipseWidth - labelPadding, nodeFontSize) +
      '\n' +
      fittingString(node.version, ellipseWidth - labelPadding, nodeFontSize);

    const half = len / 2 - 0.5;
    node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap);
    node.y = currentNode.y! - nodeHeight - vGap;
    nodes.push(node);
    datasetNodes.push(node);
    edges.push({
      source: currentNode.id,
      target: node.id,
      sourceAnchor: 2,
      targetAnchor: 3,
      type: 'cubic-vertical',
    });
  });
};

// 将模型依赖数据转换成 G6 的数据
const addProjectDependency = (
  data: ModelDepsData,
  currentNode: NodeConfig,
  nodes: NodeConfig[],
  edges: EdgeConfig[],
) => {
  const { repo_id, model_meta } = data;
  const { project_depency } = model_meta;
  if (project_depency?.url) {
    const node = { ...project_depency };
    node.id = `$P_${repo_id}_${node.url}_${node.branch}`;
    node.model_type = NodeType.Project;
    node.type = 'rect';
    node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize);
    node.style = getStyle(NodeType.Project);
    node.style.radius = nodeHeight / 2;
    node.x = currentNode.x;
    node.y = currentNode.y! + nodeHeight + vGap;

    nodes.push(node);
    edges.push({
      source: currentNode.id,
      target: node.id,
      sourceAnchor: 3,
      targetAnchor: 2,
      type: 'cubic-vertical',
    });
  }
};

/*
// 判断两个矩形是否相交
function isRectanglesOverlap(rect1: Rect, rect2: Rect) {
  const a2x = rect1.x + rect1.width / 2;
  const a2y = rect1.y + rect1.height / 2;
  const b1x = rect2.x - rect2.width / 2;
  const b1y = rect2.y - rect2.height / 2;
  return b1y <= a2y && b1x <= a2x;
}

// 判断子节点是否与数据集节点重叠
function isChildrenOverlapDataset(nodes: NodeConfig[], childrenRect: Rect) {
  for (const node of nodes) {
    const rect = { x: node.x!, y: node.y!, width: nodeWidth, height: nodeHeight };
    if (isRectanglesOverlap(rect, childrenRect)) {
      return childrenRect;
    }
  }

  return null;
}

// 调整数据集位置
function adjustDatasetPosition(node: NodeConfig) {
  const nodeRect = {
    x: node.x!,
    y: node.y!,
    width: nodeWidth,
    height: nodeHeight,
  };
  const overlapRect = isChildrenOverlapDataset(datasetNodes, nodeRect);
  if (overlapRect) {
    const adjustRect = {
      x: overlapRect.x - nodeWidth - hGap / 2,
      y: overlapRect.y,
      width: overlapRect.width,
      height: overlapRect.height,
    };
    const lastNode = datasetNodes[datasetNodes.length - 1];
    const distance = lastNode.x! - adjustRect.x;
    datasetNodes.forEach((item) => {
      item.x = item.x! - distance;
    });
  }
}
*/

// 层级遍历树结构
export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] {
  if (!data) return [];
  let level = 0;
  data.level = level;
  const result: ModelDepsData[] = [data];
  let index = 0;

  while (index < result.length) {
    const item = result[index];
    if (item.children) {
      item.children.forEach((child) => {
        child.level = item.level + 1;
        result.push(child);
      });
    }
    index++;
  }

  return result;
}

// 找到同层次的下一个节点
export function getSameHierarchyNextNode(node: ModelDepsData, nodes: ModelDepsData[]) {
  const index = nodes.findIndex((item) => item.id === node.id);
  if (index >= 0 && index < nodes.length - 1) {
    const nextNode = nodes[index + 1];
    if (nextNode.level === node.level) {
      return nextNode;
    }
  }
  return null;
}

// 得到层级的宽度
export function getHierarchyWidth(level: number, nodes: ModelDepsData[]) {
  const hierarchyNodes = nodes
    .filter((item) => item.level === level && item.expanded === true)
    .sort((a, b) => b.datasetLen - a.datasetLen);
  const first = hierarchyNodes[0];
  if (first) {
    return Math.max(((first.datasetLen - 1) * (nodeWidth + datasetHGap)) / 2, 0);
  }
  return 0;
}
