// 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/allMapData Process map data store for fetching,
 *   filtering, and managing Discover Map page state including
 *   filter rules, data layers, and process map data.
 */

import { defineStore } from "pinia";
import moment from "moment";
import apiClient from "@/api/client.js";
import apiError from "@/module/apiError.js";
import { Decimal } from "decimal.js";

/**
 * Returns the API base path for the current map data source,
 * prioritizing temp filter over created filter over log.
 * @param {object} state - The store state.
 * @returns {string} The API base path.
 */
function getMapApiBase(state) {
  const { tempFilterId, createFilterId, logId } = state;
  if (tempFilterId !== null) return `/api/temp-filters/${tempFilterId}`;
  if (createFilterId !== null) return `/api/filters/${createFilterId}`;
  return `/api/logs/${logId}`;
}

/** Pinia store for Discover Map page data and filter management. */
export const useAllMapDataStore = defineStore("allMapDataStore", {
  state: () => ({
    baseLogId: null,
    logId: null,
    traceId: null,
    baseTraceId: 1,
    tempFilterId: null,
    createFilterId: null,
    filterName: null,
    allProcessMap: {},
    allBpmn: {},
    allStats: {},
    allInsights: {},
    allTrace: [],
    allBaseTrace: [],
    allCase: [],
    allBaseCase: [],
    allTraceTaskSeq: [],
    allBaseTraceTaskSeq: [],
    allFilterTask: [],
    allFilterStartToEnd: [],
    allFilterEndToStart: [],
    allFilterTimeframe: {},
    allFilterTrace: [],
    allFilterAttrs: [],
    hasResultRule: null, // Whether any data remains after clicking Apply
    temporaryData: [], // Data not yet applied via Apply All
    postRuleData: [], // Data for the has-result API and temp-filters API
    ruleData: [], // Funnle view's data
    isRuleData: [], // toggle button data
    allFunnelData: [],
    isUpdateFilter: false, // Whether the filter file was saved successfully
    selectTimeFrame: [], // user select time start and end
    infinite404: null, // Whether infinite scroll has reached the last page
    infiniteStart: 0, // Starting index for infinite scroll cases
    baseInfiniteStart: 0, // Starting index for base infinite scroll cases
  }),
  getters: {
    processMap: (state) => {
      return state.allProcessMap;
    },
    bpmn: (state) => {
      return state.allBpmn;
    },
    stats: (state) => {
      return state.allStats;
    },
    insights: (state) => {
      return state.allInsights;
    },
    traces: (state) => {
      return [...state.allTrace].sort((x, y) => x.id - y.id);
    },
    baseTraces: (state) => {
      return [...state.allBaseTrace].sort((x, y) => x.id - y.id);
    },
    cases: (state) => {
      return state.allCase;
    },
    baseCases: (state) => {
      return state.allBaseCase;
    },
    infiniteFirstCases: (state) => {
      if (state.infiniteStart === 0) return state.allCase;
    },
    BaseInfiniteFirstCases: (state) => {
      if (state.baseInfiniteStart === 0) return state.allBaseCase;
    },
    traceTaskSeq: (state) => {
      return state.allTraceTaskSeq;
    },
    baseTraceTaskSeq: (state) => {
      return state.allBaseTraceTaskSeq;
    },
    // All tasks
    filterTasks: (state) => {
      return state.allFilterTask;
    },
    // form start to end tasks
    filterStartToEnd: (state) => {
      return state.allFilterStartToEnd;
    },
    // form end to start tasks
    filterEndToStart: (state) => {
      return state.allFilterEndToStart;
    },
    filterTimeframe: (state) => {
      return state.allFilterTimeframe;
    },
    filterTrace: (state) => {
      return state.allFilterTrace;
    },
    filterAttrs: (state) => {
      if (state.allFilterAttrs !== null) {
        return state.allFilterAttrs.map((att) => {
          const copy = { ...att };
          switch (copy.type) {
            case "date":
              copy.min =
                copy.min === null
                  ? null
                  : moment(copy.min).format("YYYY/MM/DD HH:mm");
              copy.max =
                copy.max === null
                  ? null
                  : moment(copy.max).format("YYYY/MM/DD HH:mm");
              break;
            case "float":
              copy.min =
                copy.min === null
                  ? null
                  : Number(new Decimal(copy.min).toFixed(2, 3));
              copy.max =
                copy.max === null
                  ? null
                  : Number(new Decimal(copy.max).toFixed(2, 2));
              break;
            default:
              break;
          }
          return copy;
        });
      }
      return [];
    },
    allFunnels: (state) => {
      return state.allFunnelData;
    },
  },
  actions: {
    /**
     * fetch discover api, include '/process-map, /bpmn, /stats, /insights'.
     */
    async getAllMapData() {
      const api = `${getMapApiBase(this)}/discover`;
      try {
        const response = await apiClient.get(api);
        this.allProcessMap = response.data.process_map;
        this.allBpmn = response.data.bpmn;
        this.allStats = response.data.stats;
        this.allInsights = response.data.insights;
      } catch (error) {
        apiError(error, "Failed to load the Map.");
      }
    },
    /**
     * fetch trace api.
     */
    async getAllTrace() {
      const baseLogId = this.baseLogId;
      const baseApi = `/api/logs/${baseLogId}/traces`;
      const api = `${getMapApiBase(this)}/traces`;
      try {
        let baseResponse;
        const response = await apiClient.get(api);
        this.allTrace = response.data;
        if (baseLogId) {
          baseResponse = await apiClient.get(baseApi);
          this.allBaseTrace = baseResponse.data;
        }
      } catch (error) {
        apiError(error, "Failed to load the Trace.");
      }
    },
    /**
     * fetch trace detail api.
     */
    async getTraceDetail() {
      const traceId = this.traceId;
      const start = this.infiniteStart;
      const api = `${getMapApiBase(this)}/traces/${traceId}?start=${start}&page_size=20`;
      try {
        const response = await apiClient.get(api);
        this.allTraceTaskSeq = response.data.task_seq;
        this.allCase = response.data.cases;
        this.allCase.forEach((c) => {
          c.started_at = moment(c.started_at).format("YYYY/MM/DD HH:mm");
          c.completed_at = moment(c.completed_at).format("YYYY/MM/DD HH:mm");
          c.attributes.forEach((att) => {
            switch (att.type) {
              case "date":
                att.value =
                  att.value === null
                    ? null
                    : moment(att.value).format("YYYY/MM/DD HH:mm");
                break;
              case "float":
                att.value =
                  att.value === null
                    ? null
                    : Number(new Decimal(att.value).toFixed(2));
                break;
              default:
                break;
            }
          });
        });
        return this.allCase;
      } catch (error) {
        if (error.response?.status === 404) {
          this.infinite404 = 404;
          return;
        }
        apiError(error, "Failed to load the Trace Detail.");
        return [];
      }
    },
    /**
     * fetch base log trace detail api.
     */
    async getBaseTraceDetail() {
      const logId = this.baseLogId;
      const traceId = this.baseTraceId;
      const start = this.baseInfiniteStart;
      const api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`;

      try {
        const response = await apiClient.get(api);
        this.allBaseTraceTaskSeq = response.data.task_seq;
        this.allBaseCase = response.data.cases;
        this.allBaseCase.forEach((c) => {
          c.started_at = moment(c.started_at).format("YYYY/MM/DD HH:mm");
          c.completed_at = moment(c.completed_at).format("YYYY/MM/DD HH:mm");
          c.attributes.forEach((att) => {
            switch (att.type) {
              case "date":
                att.value =
                  att.value === null
                    ? null
                    : moment(att.value).format("YYYY/MM/DD HH:mm");
                break;
              case "float":
                att.value =
                  att.value === null
                    ? null
                    : Number(new Decimal(att.value).toFixed(2));
                break;
              default:
                break;
            }
          });
        });
        return this.allBaseCase;
      } catch (error) {
        if (error.response?.status === 404) {
          this.infinite404 = 404;
          return;
        }
        apiError(error, "Failed to load the Base Trace Detail.");
        return [];
      }
    },
    /**
     * fetch Filter Parameters api.
     */
    async getFilterParams() {
      const logId = this.logId;
      const api = `/api/filters/params?log_id=${logId}`;

      try {
        const response = await apiClient.get(api);
        this.allFilterTask = response.data.tasks;
        this.allFilterStartToEnd = response.data.sources;
        this.allFilterEndToStart = response.data.sinks;
        this.allFilterTimeframe = response.data.timeframe;
        this.allFilterTrace = response.data.trace;
        this.allFilterAttrs = response.data.attrs;

        const min = this.allFilterTimeframe.x_axis.min;
        const max = this.allFilterTimeframe.x_axis.max;
        // Preserve raw data for Chart.js; incorrect format causes wrong output
        this.allFilterTimeframe.x_axis.min_base = min;
        this.allFilterTimeframe.x_axis.max_base = max;
        // Convert to a time format without seconds
        this.allFilterTimeframe.x_axis.min =
          min === null ? null : moment(min).format("YYYY/MM/DD HH:mm");
        this.allFilterTimeframe.x_axis.max =
          max === null ? null : moment(max).format("YYYY/MM/DD HH:mm");
      } catch (error) {
        apiError(error, "Failed to load the Filter Parameters.");
      }
    },
    /**
     * Test if the Filter Rules Result in Any Data
     */
    async checkHasResult() {
      const logId = this.logId;
      const api = `/api/filters/has-result?log_id=${logId}`;

      try {
        const response = await apiClient.post(api, this.postRuleData);
        this.hasResultRule = response.data.result;
      } catch (error) {
        apiError(error, "Failed to load the Has Result.");
      }
    },
    /**
     * Add a New Temporary Filter
     */
    async addTempFilterId() {
      const logId = this.logId;
      const api = `/api/temp-filters?log_id=${logId}`;

      try {
        const response = await apiClient.post(api, this.postRuleData);
        this.tempFilterId = response.data.id;
      } catch (error) {
        apiError(error, "Failed to add the Temporary Filters.");
      }
    },
    /**
     * Add a New Filter
     * @param {string} value file's name
     */
    async addFilterId(value) {
      const logId = this.logId;
      const api = `/api/filters?log_id=${logId}`;
      const createFilterObj = {
        name: value,
        rules: this.postRuleData,
      };

      try {
        const response = await apiClient.post(api, createFilterObj);
        this.createFilterId = response.data.id;
        this.tempFilterId = null;
      } catch (error) {
        apiError(error, "Failed to load the Filters.");
      }
    },
    /**
     * Get Filter Detail
     * @param {number} createFilterId filter type ID
     */
    async fetchFunnel(createFilterId) {
      const api = `/api/filters/${createFilterId}`;

      if (createFilterId) {
        try {
          const response = await apiClient.get(api);
          this.temporaryData = response.data.rules;
          this.logId = response.data.log?.id;
          this.filterName = response.data.name;
          this.baseLogId = response.data.log?.id;
        } catch (error) {
          apiError(error, "Failed to get Filter Detail.");
        }
      }
    },
    /**
     * Update an Existing Filter
     */
    async updateFilter() {
      const createFilterId = this.createFilterId;
      const api = `/api/filters/${createFilterId}`;
      const data = this.postRuleData;

      try {
        const response = await apiClient.put(api, data);
        this.isUpdateFilter = response.status === 200;
        this.tempFilterId = null;
      } catch (error) {
        apiError(error, "Failed to update an Existing Filter.");
      }
    },
  },
});
