<template>
  <Dialog
    :visible="listModal"
    @update:visible="emit('closeModal', $event)"
    modal
    :style="{ width: '90vw', height: '90vh' }"
    :contentClass="contentClass"
  >
    <template #header>
      <div class="py-5">
        <p class="text-base font-bold">Non-conformance Issue</p>
      </div>
    </template>
    <div class="h-full flex items-start justify-start p-4">
      <!-- Trace List -->
      <section class="w-80 h-full pr-4">
        <p class="h2 px-2 mb-2">Trace List ({{ traceTotal }})</p>
        <p class="text-primary h2 px-2 mb-2">
          <span class="material-symbols-outlined !text-sm align-[-10%] mr-2"
            >info</span
          >Click trace number to see more.
        </p>
        <div
          class="overflow-y-scroll overflow-x-hidden scrollbar mx-[-8px] max-h-[calc(100%_-_96px)]"
        >
          <table class="border-separate border-spacing-x-2 text-sm">
            <caption class="hidden">
              Trace List
            </caption>
            <thead class="sticky top-0 z-10 bg-neutral-100">
              <tr>
                <th class="h2 px-2 border-b border-neutral-500">Trace</th>
                <th
                  class="h2 px-2 border-b border-neutral-500 text-start"
                  colspan="3"
                >
                  Occurrences
                </th>
              </tr>
            </thead>
            <tbody>
              <tr
                v-for="(trace, key) in traceList"
                :key="key"
                class="cursor-pointer hover:text-primary"
                @click="switchCaseData(trace.id)"
              >
                <td class="p-2">#{{ trace.id }}</td>
                <td class="p-2 w-24">
                  <div
                    class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden"
                  >
                    <div
                      class="h-full bg-primary"
                      :style="progressWidth(trace.value)"
                    ></div>
                  </div>
                </td>
                <td class="py-2 text-right">{{ trace.count }}</td>
                <td class="p-2 text-right">{{ trace.ratio }}%</td>
              </tr>
            </tbody>
          </table>
        </div>
      </section>
      <!-- Trace item Table -->
      <section
        class="px-4 py-2 h-full w-[calc(100%_-_320px)] bg-neutral-10 rounded-xl"
      >
        <p class="h2 mb-2 px-4">Trace #{{ showTraceId }}</p>
        <div class="h-36 w-full px-2 mb-2 border border-neutral-300 rounded">
          <div class="h-full w-full">
            <div
              id="cfmTrace"
              ref="cfmTrace"
              class="h-full min-w-full relative"
            ></div>
          </div>
        </div>
        <div
          class="overflow-y-auto overflow-x-auto scrollbar h-[calc(100%_-_200px)] infiniteTable"
          @scroll="handleScroll"
        >
          <DataTable
            :value="caseData"
            showGridlines
            tableClass="text-sm"
            breakpoint="0"
          >
            <div v-for="col in columnData" :key="col.field">
              <Column :field="col.field" :header="col.header">
                <template #body="{ data }">
                  <div
                    :class="
                      data[col.field]?.length > 18
                        ? 'whitespace-normal'
                        : 'whitespace-nowrap'
                    "
                  >
                    {{ data[col.field] }}
                  </div>
                </template>
              </Column>
            </div>
          </DataTable>
        </div>
      </section>
    </div>
  </Dialog>
</template>
<script setup>
// The Lucia project.
// Copyright 2023-2026 DSP, inc.  All rights reserved.
// Authors:
//   chiayin.kuo@dsp.im (chiayin), 2023/1/31
//   cindy.chang@dsp.im (Cindy Chang), 2024/5/30
//   imacat.yang@dsp.im (imacat), 2023/9/23
/**
 * @module components/Discover/Conformance/MoreModal
 *   Modal dialog showing detailed conformance check
 *   results with expandable activity sequences.
 */

import { ref, computed, watch, nextTick, useTemplateRef, onBeforeUnmount } from "vue";
import { storeToRefs } from "pinia";
import { useConformanceStore } from "@/stores/conformance";
import cytoscapeMapTrace from "@/module/cytoscapeMapTrace.js";

const props = defineProps([
  "listModal",
  "listNo",
  "traceId",
  "firstCases",
  "listTraces",
  "taskSeq",
  "cases",
  "category",
]);
const emit = defineEmits(["closeModal"]);

const conformanceStore = useConformanceStore();
const { infinite404 } = storeToRefs(conformanceStore);

// template ref
const cfmTrace = useTemplateRef("cfmTrace");

// data
const contentClass = ref("!bg-neutral-100 border-t border-neutral-300 h-full");
const showTraceId = ref(null);
const infiniteData = ref(null);
const maxItems = ref(false);
const infiniteFinish = ref(true); // Whether infinite scroll loading is complete
const startNum = ref(0);
const cyTraceInstance = ref(null);
const processMap = ref({
  nodes: [],
  edges: [],
});

// computed
const traceTotal = computed(() => {
  return traceList.value.length;
});

const traceList = computed(() => {
  const sum = props.listTraces
    .map((trace) => trace.count)
    .reduce((acc, cur) => acc + cur, 0);
  if (sum === 0) return [];

  return props.listTraces
    .map((trace) => {
      return {
        id: trace.id,
        value: Number(getPercentLabel(trace.count / sum)),
        count: trace.count.toLocaleString("en-US"),
        count_base: trace.count,
        ratio: getPercentLabel(trace.count / sum),
      };
    })
    .sort((x, y) => x.id - y.id);
});

const caseData = computed(() => {
  if (infiniteData.value !== null) {
    const data = JSON.parse(JSON.stringify(infiniteData.value)); // Deep copy the original cases data
    data.forEach((item) => {
      item.facets.forEach((facet, index) => {
        item[`fac_${index}`] = facet.value; // Create a new key-value pair
      });
      delete item.facets; // Remove the original facets property

      item.attributes.forEach((attribute, index) => {
        item[`att_${index}`] = attribute.value; // Create a new key-value pair
      });
      delete item.attributes; // Remove the original attributes property
    });
    return data;
  }
  return [];
});

const columnData = computed(() => {
  const data = JSON.parse(JSON.stringify(props.cases)); // Deep copy the original cases data
  if (!data || data.length === 0) return [];
  const facetName = (facName) =>
    facName
      .trim()
      .replace(
        /^(.)(.*)$/,
        (match, firstChar, restOfString) =>
          firstChar.toUpperCase() + restOfString.toLowerCase(),
      );

  const result = [
    { field: "id", header: "Case Id" },
    { field: "started_at", header: "Start time" },
    { field: "completed_at", header: "End time" },
    ...(data[0].facets ?? []).map((fac, index) => ({
      field: `fac_${index}`,
      header: facetName(fac.name),
    })),
    ...(data[0].attributes ?? []).map((att, index) => ({
      field: `att_${index}`,
      header: att.key,
    })),
  ];
  return result;
});

// watch
watch(
  () => props.listModal,
  (newValue) => {
    // Draw the chart when the modal is opened for the first time
    if (newValue) createCy();
  },
);

watch(
  () => props.taskSeq,
  (newValue) => {
    if (newValue !== null) createCy();
  },
);

watch(
  () => props.traceId,
  (newValue) => {
    // Update showTraceId when the traceId prop changes
    showTraceId.value = newValue;
  },
);

watch(showTraceId, (newValue, oldValue) => {
  const isScrollTop = document.querySelector(".infiniteTable");
  if (isScrollTop && typeof isScrollTop.scrollTop !== "undefined")
    if (newValue !== oldValue) isScrollTop.scrollTop = 0;
});

watch(
  () => props.firstCases,
  (newValue) => {
    infiniteData.value = newValue;
  },
);

watch(infinite404, (newValue) => {
  if (newValue === 404) maxItems.value = true;
});

// methods
/**
 * Number to percentage
 * @param   {number} val - The raw ratio value.
 * @returns {string} The formatted percentage string.
 */
function getPercentLabel(val) {
  if (Number((val * 100).toFixed(1)) >= 100) return 100;
  else return parseFloat((val * 100).toFixed(1));
}
/**
 * set progress bar width
 * @param   {number} value - The percentage value.
 * @returns {string} The CSS width style string.
 */
function progressWidth(value) {
  return `width:${value}%;`;
}
/**
 * switch case data
 * @param {number} id case id
 */
async function switchCaseData(id) {
  if (id === showTraceId.value) return;
  infinite404.value = null;
  maxItems.value = false;
  startNum.value = 0;

  let result = null;
  if (props.category === "issue")
    result = await conformanceStore.getConformanceTraceDetail(
      props.listNo,
      id,
      0,
    );
  else if (props.category === "loop")
    result = await conformanceStore.getConformanceLoopsTraceDetail(
      props.listNo,
      id,
      0,
    );
  infiniteData.value = result;
  showTraceId.value = id; // Set after getDetail so the case table finishes loading before switching showTraceId
}
/**
 * Assembles the trace element nodes data for Cytoscape rendering.
 */
function setNodesData() {
  // Clear nodes to prevent accumulation on each render
  processMap.value.nodes = [];
  // Populate nodes with data returned from the API call
  if (props.taskSeq !== null) {
    props.taskSeq.forEach((node, index) => {
      processMap.value.nodes.push({
        data: {
          id: index,
          label: node,
          backgroundColor: "#CCE5FF",
          bordercolor: "#003366",
          shape: "round-rectangle",
          height: 80,
          width: 100,
        },
      });
    });
  }
}
/**
 * Assembles the trace edge line data for Cytoscape rendering.
 */
function setEdgesData() {
  processMap.value.edges = [];
  if (props.taskSeq !== null) {
    props.taskSeq.forEach((edge, index) => {
      processMap.value.edges.push({
        data: {
          source: `${index}`,
          target: `${index + 1}`,
          lineWidth: 1,
          style: "solid",
        },
      });
    });
  }
  // The number of edges is one less than the number of nodes
  processMap.value.edges.pop();
}
/**
 * create trace cytoscape's map
 */
function createCy() {
  nextTick(() => {
    const graphId = cfmTrace.value;

    setNodesData();
    setEdgesData();
    cyTraceInstance.value?.destroy();
    if (graphId !== null)
      cyTraceInstance.value = cytoscapeMapTrace(
        processMap.value.nodes,
        processMap.value.edges,
        graphId,
      );
  });
}
/**
 * Infinite scroll: loads more data.
 */
async function fetchData() {
  try {
    infiniteFinish.value = false;
    startNum.value += 20;
    const result = await conformanceStore.getConformanceTraceDetail(
      props.listNo,
      showTraceId.value,
      startNum.value,
    );
    if (result) infiniteData.value = [...infiniteData.value, ...result];
    infiniteFinish.value = true;
  } catch (error) {
    console.error("Failed to load data:", error);
    infiniteFinish.value = true;
  }
}
/**
 * Infinite scroll: listens for scroll reaching the bottom.
 * @param {Event} event - The scroll event.
 */
function handleScroll(event) {
  if (
    maxItems.value ||
    (infiniteData.value?.length ?? 0) < 20 ||
    infiniteFinish.value === false
  )
    return;

  const container = event.target;
  const overScrollHeight =
    container.scrollTop + container.clientHeight + 20 >= container.scrollHeight;

  if (overScrollHeight) fetchData();
}

onBeforeUnmount(() => {
  cyTraceInstance.value?.destroy();
});
</script>
<style scoped>
@reference "../../../assets/tailwind.css";
/* Progress bar color */
:deep(.p-progressbar .p-progressbar-value) {
  @apply bg-primary;
}
/* Table set */
:deep(.p-datatable-thead) {
  @apply sticky top-0 left-0 z-10 bg-neutral-10;
}
:deep(.p-datatable .p-datatable-thead > tr > th) {
  @apply !border-y-0 border-neutral-500 bg-neutral-100 after:absolute after:left-0 after:w-full after:h-full after:block after:top-0 after:border-b after:border-t after:border-neutral-500;
  white-space: nowrap;
}
:deep(.p-datatable .p-datatable-tbody > tr > td) {
  @apply border-neutral-500 !border-t-0 text-center;
}
/* Center header title */
:deep(.p-column-header-content) {
  @apply justify-center;
}
:deep(.p-datatable.p-datatable-gridlines .p-datatable-tbody > tr > td) {
  min-width: 72px;
  max-width: 184px;
  overflow-wrap: break-word;
  word-wrap: break-word;
}
</style>
