// The Lucia project.
// Copyright 2023-2026 DSP, inc.  All rights reserved.
// Authors:
//   chiayin.kuo@dsp.im (chiayin), 2023/1/31
//   imacat.yang@dsp.im (imacat), 2023/9/23
//   cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/**
 * @module stores/mapPathStore Cytoscape map interaction store for
 *   node/edge click highlighting with path insights visualization.
 */

import { defineStore } from 'pinia';
import { useAllMapDataStore } from '@/stores/allMapData';
import { INSIGHTS_FIELDS_AND_LABELS } from '@/constants/constants';
import ImgCapsuleGlow1 from '@/assets/capsule1-glow.svg';
import ImgCapsuleGlow2 from '@/assets/capsule2-glow.svg';
import ImgCapsuleGlow3 from '@/assets/capsule3-glow.svg';
import ImgCapsuleGlow4 from '@/assets/capsule4-glow.svg';
import ImgCapsule1 from '@/assets/capsule1.svg';
import ImgCapsule2 from '@/assets/capsule2.svg';
import ImgCapsule3 from '@/assets/capsule3.svg';
import ImgCapsule4 from '@/assets/capsule4.svg';

const ImgCapsulesGlow = [ImgCapsuleGlow1, ImgCapsuleGlow2, ImgCapsuleGlow3, ImgCapsuleGlow4];
const ImgCapsules = [ImgCapsule1, ImgCapsule2, ImgCapsule3, ImgCapsule4];

/**
 * Builds insight paths for a single field by linking graph nodes/edges
 * to the insight data entries.
 */
function buildInsightPathsForField(fieldKey, curButton, startNode, insightWithPath) {
  insightWithPath[fieldKey] = {};
  for (let listIndex = 0; listIndex < curButton.length; listIndex++) {
    insightWithPath[fieldKey][listIndex] = { edges: [], nodes: [] };
    let curGraphNode, prevGraphNode, curEdge;
    for (let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++) {
      if (nodeIndex === 0) {
        curGraphNode = startNode.outgoers('node').filter(neighborOfStart =>
          neighborOfStart.data('label') === curButton[listIndex][nodeIndex]
        );
        curEdge = startNode.edgesTo(curGraphNode);
      } else if (prevGraphNode) {
        curGraphNode = prevGraphNode.outgoers('node').filter(neighbor =>
          neighbor.data('label') === curButton[listIndex][nodeIndex]
        );
        curEdge = prevGraphNode.edgesWith(curGraphNode);
      }
      insightWithPath[fieldKey][listIndex].nodes.push(curGraphNode);
      insightWithPath[fieldKey][listIndex].edges.push(curEdge);
      if (nodeIndex === curButton[listIndex].length - 1) {
        const endNode = curGraphNode.outgoers('node').filter(neighbor =>
          neighbor.data('label').toLowerCase() === 'end'
        );
        const lastEdge = curGraphNode.edgesWith(endNode);
        insightWithPath[fieldKey][listIndex].edges.push(lastEdge);
      }
      prevGraphNode = curGraphNode;
    }
  }
}

/**
 * Matches a single graph path against all insight button entries
 * for a specific field.
 */
function matchPathAgainstButton(curPath, curPathByEdge, curButton, fieldIndex, mapGraphPathToInsight) {
  for (let listIndex = 0; listIndex < curButton.length; listIndex++) {
    for (let nodeIndex = 0; nodeIndex < curButton[listIndex].length; nodeIndex++) {
      if (curPath[1].data('label') === curButton[listIndex][nodeIndex]) {
        const matchResult = depthFirstSearchMatchTwoPaths(curPath, 1, curButton, listIndex, nodeIndex);
        if (matchResult) {
          mapGraphPathToInsight[fieldIndex] = {
            [listIndex]: {
              pathByNode: [...curPath],
              pathByEdge: [...curPathByEdge],
              pathType: INSIGHTS_FIELDS_AND_LABELS[fieldIndex][0],
            }
          };
        }
      }
    }
  }
}

/**
 * Recursively checks whether two paths match by comparing node labels
 * along both paths using depth-first search.
 */
function depthFirstSearchMatchTwoPaths(curPath, curPathIndex, curButton, listIndex, nodeIndex) {
  if (listIndex >= curButton.length) {
    return false;
  }
  if (nodeIndex >= curButton[listIndex].length) {
    return false;
  }
  if (curPathIndex === curPath.length || nodeIndex === curButton[listIndex].length) {
    return true;
  }
  if (curPathIndex >= curPath.length || nodeIndex >= curButton[listIndex].length) {
    return false;
  }

  const nodeLabel = curPath[curPathIndex].data('label');
  if (nodeLabel !== curButton[listIndex][nodeIndex]) {
    return false;
  }
  if (nodeIndex === curButton[listIndex].length - 1) {
    return true;
  }
  return depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex + 1, nodeIndex)
    || depthFirstSearchMatchTwoPaths(curPath, curPathIndex + 1, curButton, listIndex, nodeIndex + 1);
}

export const useMapPathStore = defineStore('mapPathStore', {
  state: () => ({
    clickedPath: [],
    insights: {},
    insightWithPath: {},
    cytoscape: {
      process:
      {
        curved: {
          horizontal: null,
          vertical: null,
        },
        elbow: {
          horizontal: null,
          vertical: null,
        }
      },
      bpmn: {
        curved: {
          horizontal: null,
          vertical: null,
        },
        elbow: {
          horizontal: null,
          vertical: null,
        }
      }
    },
    processOrBPMN: 'process',
    curveType: 'curved',
    directionType: 'horizontal',
    allPaths: [],
    allPathsByEdge: [],
    startNode: null,
    mapGraphPathToInsight: {},
    activeTrace: 0,
    activeListIndex: 0,
    lastClickedNode: null,
    isBPMNOn: false,
  }),
  actions: {
    async setCytoscape(cytoscape, processOrBPMN = 'process', curveType = 'curved', directionType = 'horizontal') {
      this.processOrBPMN = processOrBPMN;
      this.curveType = curveType;
      this.directionType = directionType;
      this.cytoscape[processOrBPMN][curveType][directionType] = cytoscape;
      await this.createInsightWithPath();
      if (processOrBPMN === 'process') {
        await this.highlightMostFrequentPath();
      }
    },
    async createInsightWithPath() {
      const { insights } = useAllMapDataStore();
      this.insights = { ...insights };
      this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes()
        .filter(function (elem) {
          return elem.data('label').toLowerCase() === 'start';
        });
      for (const [, fieldAndLabel] of INSIGHTS_FIELDS_AND_LABELS.entries()) {
        const curButton = this.insights[fieldAndLabel[0]];
        if (!curButton) continue;
        buildInsightPathsForField(fieldAndLabel[0], curButton, this.startNode, this.insightWithPath);
      }
    },
    async createPaths() {
      this.startNode = this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes()
        .filter(function (elem) {
          return elem.data('label').toLowerCase() === 'start';
        });
      // Depth First Search from the starting node
      this.depthFirstSearchCreatePath(this.startNode, [this.startNode], []);
      const { insights } = useAllMapDataStore();
      this.insights = { ...insights };
      this.matchGraphPathWithInsightsPath();
    },
    /**
     * Builds all paths from the start node using depth-first search.
     * @param node - The current node.
     * @param currentPathByNode - Accumulated nodes along the path.
     * @param curPathByEdge - Accumulated edges along the path.
     */
    depthFirstSearchCreatePath(node, currentPathByNode, curPathByEdge) {
      const outgoingEdges = node.outgoers('edge');
      if (outgoingEdges.length === 0) {
        // Reached the end node
        this.allPaths.push([...currentPathByNode]);
        this.allPathsByEdge.push([...curPathByEdge])
      } else {
        outgoingEdges.targets().forEach((targetNode) => {
          if (!currentPathByNode.includes(targetNode)) {
            const connectingEdge = targetNode.edgesWith(currentPathByNode[currentPathByNode.length - 1]);
            // Avoid loops: only continue if the target node is not already in the current path
            this.depthFirstSearchCreatePath(targetNode, [...currentPathByNode, targetNode],
              [...curPathByEdge, connectingEdge]
            );
          }
        });
      }
    },
    /**
     * Matches graph paths with insights paths by comparing
     * node labels along each path to the insights data arrays.
     */
    matchGraphPathWithInsightsPath() {
      for (let whichPath = 0; whichPath < this.allPaths.length; whichPath++) {
        const curPath = this.allPaths[whichPath];
        if (curPath.length < 2) continue;
        const curPathByEdge = this.allPathsByEdge[whichPath];
        for (let i = 0; i < INSIGHTS_FIELDS_AND_LABELS.length; i++) {
          const curButton = this.insights[INSIGHTS_FIELDS_AND_LABELS[i][0]];
          if (!curButton) continue;
          matchPathAgainstButton(curPath, curPathByEdge, curButton, i, this.mapGraphPathToInsight);
        }
      }
    },
    highlightClickedPath(clickedActiveTraceIndex: number, clickedPathListIndex: number) {
      const key = INSIGHTS_FIELDS_AND_LABELS[clickedActiveTraceIndex]?.[0];
      const path = this.insightWithPath?.[key]?.[clickedPathListIndex];
      if (!path) return;
      path.edges.forEach(edgeToHighlight => {
        edgeToHighlight.addClass('highlight-edge');
      });
      path.nodes.forEach(nodeToHighlight => {
        nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]);
      });
    },
clearAllHighlight() {
      this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.edges().removeClass('highlight-edge');
      this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes().removeClass('highlight-node');
      this.cytoscape[this.processOrBPMN][this.curveType][this.directionType]?.nodes().forEach(nodeToReset => {
        nodeToReset.data('nodeImageUrl', ImgCapsules[nodeToReset.data('level')])
      });
    },
    onNodeClickHighlightEdges(clickedNode) {
      this.clearAllHighlight();
      clickedNode.addClass('highlight-node');
      clickedNode.data('nodeImageUrl', ImgCapsulesGlow[clickedNode.data('level')]);
      clickedNode.outgoers('edge').forEach(edgeToHighlight => edgeToHighlight.addClass('highlight-edge'));
      clickedNode.incomers('edge').forEach(edgeToHighlight => edgeToHighlight.addClass('highlight-edge'));
      this.lastClickedNode = clickedNode;
    },
    onEdgeClickHighlightNodes(clickedEdge) {
      this.clearAllHighlight();

      const sourceNode = clickedEdge.source();
      const targetNode = clickedEdge.target();

      sourceNode.addClass('highlight-node');
      targetNode.addClass('highlight-node');

      sourceNode.data('nodeImageUrl', ImgCapsulesGlow[sourceNode.data('level')]);
      targetNode.data('nodeImageUrl', ImgCapsulesGlow[targetNode.data('level')]);

      clickedEdge.addClass('highlight-edge');
    },
    async highlightMostFrequentPath() {
      const LIST_INDEX = 0;
      const traces = this.insightWithPath?.['most_freq_traces'];
      if (!traces?.[LIST_INDEX]) return;
      traces[LIST_INDEX].nodes.forEach(nodeToHighlight => {
        nodeToHighlight.data('nodeImageUrl', ImgCapsulesGlow[nodeToHighlight.data('level')]);
      });
      traces[LIST_INDEX].edges.forEach(edgeToHighlight =>
        edgeToHighlight.addClass('highlight-edge'));
    },
setIsBPMNOn(isOn: boolean) {
      this.isBPMNOn = isOn;
    },
  },
});