<template>
  <div
    class="bg-neutral-10 border border-neutral-300 rounded-xl px-4 w-full h-full"
  >
    <section class="pt-2 pb-20 space-y-2 text-sm min-w-[48%] h-full">
      <p class="h2">Range Selection</p>
      <div class="text-primary h2 flex items-center justify-start">
        <span class="material-symbols-outlined mr-2 !text-base">info</span>
        <p>Select or fill in a time range.</p>
      </div>
      <div class="chartContainer h-3/5 relative">
        <canvas id="chartCanvasId"></canvas>
        <div id="chart-mask-left" class="absolute bg-neutral-10/50"></div>
        <div id="chart-mask-right" class="absolute bg-neutral-10/50"></div>
      </div>
      <div class="px-2 py-3">
        <Slider
          v-model="selectArea"
          :step="1"
          :min="0"
          :max="selectRange"
          range
          class="mx-2"
          @change="changeSelectArea($event)"
        />
      </div>
      <!-- Calendar group -->
      <div class="flex justify-center items-center space-x-2 w-full">
        <div>
          <span class="block mb-2">Start time</span>
          <DatePicker
            v-model="startTime"
            dateFormat="yy/mm/dd"
            :panelProps="panelProps"
            :minDate="startMinDate"
            :maxDate="startMaxDate"
            showTime
            showIcon
            hourFormat="24"
            @date-select="sliderTimeRange($event, 'start')"
            id="startCalendar"
          />
        </div>
        <span class="block mt-4">~</span>
        <div>
          <span class="block mb-2">End time</span>
          <DatePicker
            v-model="endTime"
            dateFormat="yy/mm/dd"
            :panelProps="panelProps"
            :minDate="endMinDate"
            :maxDate="endMaxDate"
            showTime
            showIcon
            hourFormat="24"
            @date-select="sliderTimeRange($event, 'end')"
            id="endCalendar"
          />
        </div>
      </div>
      <!-- End calendar group -->
    </section>
  </div>
</template>
<script setup>
// The Lucia project.
// Copyright 2023-2026 DSP, inc.  All rights reserved.
// Authors:
//   chiayin.kuo@dsp.im (chiayin), 2023/1/31
/**
 * @module components/Discover/Map/Filter/Timeframes
 *   Timeframe filter with date range pickers and
 *   duration range selectors.
 */

import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import { storeToRefs } from "pinia";
import { useAllMapDataStore } from "@/stores/allMapData";
import { Chart, registerables } from "chart.js";
import "chartjs-adapter-moment";
import getMoment from "moment";

const props = defineProps(["selectValue"]);

const allMapDataStore = useAllMapDataStore();
const { filterTimeframe, selectTimeFrame } = storeToRefs(allMapDataStore);

const selectRange = ref(1000); // Number of divisions for the selection range
const selectArea = ref(null);
const chart = ref(null);
const canvasId = ref(null);
const startTime = ref(null);
const endTime = ref(null);
const startMinDate = ref(null);
const startMaxDate = ref(null);
const endMinDate = ref(null);
const endMaxDate = ref(null);
const panelProps = ref({
  onClick: (event) => {
    event.stopPropagation();
  },
});

// user select time start and end
const timeFrameStartEnd = computed(() => {
  if (!startTime.value || !endTime.value) {
    return [];
  }
  const start = getMoment(startTime.value).format("YYYY-MM-DDTHH:mm:00");
  const end = getMoment(endTime.value).format("YYYY-MM-DDTHH:mm:00");

  return [start, end];
});

watch(timeFrameStartEnd, (newValue) => {
  if (newValue.length === 2) {
    selectTimeFrame.value = newValue; // Data to send to the backend
  }
});

// Compute slider data; time format: millisecond timestamps
const sliderData = computed(() => {
  const xAxisMin = new Date(filterTimeframe.value.x_axis.min).getTime();
  const xAxisMax = new Date(filterTimeframe.value.x_axis.max).getTime();
  const range = xAxisMax - xAxisMin;
  const step = range / selectRange.value;
  const data = [];

  for (let i = 0; i <= selectRange.value; i++) {
    data.push(xAxisMin + step * i);
  }

  return data;
});

// Add the minimum and maximum values
const timeFrameData = computed(() => {
  if (!filterTimeframe.value?.data || filterTimeframe.value.data.length < 10)
    return [];
  const data = filterTimeframe.value.data.map((i) => ({ x: i.x, y: i.y }));
  // See ./public/timeFrameSlope for the y-axis slope calculation diagram
  // x values are 0 ~ 11,
  // Name three coordinates (ax, ay), (bx, by), (cx, cy) as (a, b), (c, d), (e, f)
  // Minimum: (f - b)(c - a) = (e - a)(d - b), solve for b = (ed - ad - fa - fc) / (e - c - a)
  // Maximum: (f - b)(e - c) = (f - d)(e - a), solve for f = (be - bc - de + da) / (a - c)

  // Y-axis minimum value
  const a = 0;
  let b;
  const c = 1;
  const d = filterTimeframe.value.data[0].y;
  const e = 2;
  const f = filterTimeframe.value.data[1].y;
  b = (e * d - a * d - f * a - f * c) / (e - c - a);
  if (b < 0) {
    b = 0;
  }
  // Y-axis maximum value
  const ma = 9;
  const mb = filterTimeframe.value.data[8].y;
  const mc = 10;
  const md = filterTimeframe.value.data[9].y;
  const me = 11;
  let mf = (mb * me - mb * mc - md * me + md * ma) / (ma - mc);
  if (mf < 0) {
    mf = 0;
  }

  // Add the minimum value
  data.unshift({
    x: filterTimeframe.value.x_axis.min_base,
    y: b,
  });
  // Add the maximum value
  data.push({
    x: filterTimeframe.value.x_axis.max_base,
    y: mf,
  });

  return data;
});

const labelsData = computed(() => {
  const min = new Date(filterTimeframe.value.x_axis.min_base).getTime();
  const max = new Date(filterTimeframe.value.x_axis.max_base).getTime();
  const numPoints = 11;
  const step = (max - min) / (numPoints - 1);
  const data = [];
  for (let i = 0; i < numPoints; i++) {
    const x = min + i * step;
    data.push(x);
  }
  return data;
});

watch(selectTimeFrame, (newValue, oldValue) => {
  if (newValue.length === 0) {
    startTime.value = new Date(filterTimeframe.value.x_axis.min);
    endTime.value = new Date(filterTimeframe.value.x_axis.max);
    selectArea.value = [0, selectRange.value];
    resizeMask(chart.value);
  }
});

/**
 * Adjusts the overlay mask size.
 * @param {object} chartInstance - The Chart.js instance.
 */
function resizeMask(chartInstance) {
  const from = (selectArea.value[0] * 0.01) / (selectRange.value * 0.01);
  const to = (selectArea.value[1] * 0.01) / (selectRange.value * 0.01);
  if (props.selectValue[0] === "Timeframes") {
    resizeLeftMask(chartInstance, from);
    resizeRightMask(chartInstance, to);
  }
}

/**
 * Adjusts the left overlay mask size.
 * @param {object} chartInstance - The Chart.js instance.
 */
function resizeLeftMask(chartInstance, from) {
  const canvas = document.getElementById("chartCanvasId");
  const mask = document.getElementById("chart-mask-left");
  if (!canvas || !mask) return;
  mask.style.left = `${canvas.offsetLeft + chartInstance.chartArea.left}px`;
  mask.style.width = `${chartInstance.chartArea.width * from}px`;
  mask.style.top = `${canvas.offsetTop + chartInstance.chartArea.top}px`;
  mask.style.height = `${chartInstance.chartArea.height}px`;
}

/**
 * Adjusts the right overlay mask size.
 * @param {object} chartInstance - The Chart.js instance.
 */
function resizeRightMask(chartInstance, to) {
  const canvas = document.getElementById("chartCanvasId");
  const mask = document.getElementById("chart-mask-right");
  if (!canvas || !mask) return;
  mask.style.left = `${canvas.offsetLeft + chartInstance.chartArea.left + chartInstance.chartArea.width * to}px`;
  mask.style.width = `${chartInstance.chartArea.width * (1 - to)}px`;
  mask.style.top = `${canvas.offsetTop + chartInstance.chartArea.top}px`;
  mask.style.height = `${chartInstance.chartArea.height}px`;
}

/**
 * Creates and renders the Chart.js area chart for timeframe data.
 */
function createChart() {
  const max = filterTimeframe.value.y_axis.max * 1.1;
  const minX = timeFrameData.value[0]?.x;
  const maxX = timeFrameData.value[timeFrameData.value.length - 1]?.x;

  const data = {
    labels: labelsData.value,
    datasets: [
      {
        label: "Case",
        data: timeFrameData.value,
        fill: "start",
        showLine: false,
        tension: 0.4,
        backgroundColor: "rgba(0,153,255)",
        pointRadius: 0,
        x: "x",
        y: "y",
      },
    ],
  };
  const options = {
    responsive: true,
    maintainAspectRatio: false,
    layout: {
      padding: {
        top: 16,
        left: 8,
        right: 8,
      },
    },
    plugins: {
      legend: false, // Hide legend
      filler: {
        propagate: false,
      },
      title: false,
    },
    // animations: false, // Disable animations
    animation: {
      onComplete: (e) => {
        resizeMask(e.chart);
      },
    },
    interaction: {
      intersect: true,
    },
    scales: {
      x: {
        type: "time",
        min: minX,
        max: maxX,
        ticks: {
          autoSkip: true,
          maxRotation: 0, // Do not rotate labels (0~50)
          color: "#334155",
          display: true,
          source: "labels",
        },
        grid: {
          display: false, // Hide x-axis grid lines
        },
        time: {
          minUnit: "day", // Minimum display unit
          // displayFormats: {
          //   minute: 'HH:mm MMM d',
          //   hour: 'HH:mm MMM d',
          // }
        },
      },
      y: {
        beginAtZero: true, // Scale includes 0
        max: max,
        ticks: {
          // Set tick intervals
          display: false, // Hide values, only show grid lines
          stepSize: max / 4,
        },
        grid: {
          color: "rgba(100,116,139)",
          z: 1,
        },
        border: {
          display: false, // Hide the extra border line on the left
        },
      },
    },
  };
  const config = {
    type: "line",
    data: data,
    options: options,
  };
  canvasId.value = document.getElementById("chartCanvasId");
  chart.value = new Chart(canvasId.value, config);
}

/**
 * Handles slider value changes.
 * @param {array} e [1, 100]
 */
function changeSelectArea(e) {
  // When the calendar changes, the slider follows
  const sliderDataVal = sliderData.value;
  const start = sliderDataVal[e[0].toFixed()];
  const end = sliderDataVal[e[1].toFixed()]; // Get the index, which must be an integer.

  startTime.value = new Date(start);
  endTime.value = new Date(end);
  // Reset the start/end calendar selection range
  endMinDate.value = new Date(start);
  startMaxDate.value = new Date(end);
  // Recalculate the chart mask
  resizeMask(chart.value);
}

/**
 * Updates the slider and chart when a start or end time is selected.
 * @param {object} e - The selected date object.
 * @param {string} direction start or end
 */
function sliderTimeRange(e, direction) {
  // Find the closest index; time format: millisecond timestamps
  const sliderDataVal = sliderData.value;
  const targetTime = [
    new Date(timeFrameStartEnd.value[0]).getTime(),
    new Date(timeFrameStartEnd.value[1]).getTime(),
  ];
  const closestIndexes = targetTime.map((target) => {
    let closestIndex = 0;
    closestIndex =
      ((target - sliderDataVal[0]) /
        (sliderDataVal[sliderDataVal.length - 1] - sliderDataVal[0])) *
      sliderDataVal.length;
    let result = Math.round(Math.abs(closestIndex));
    result = Math.min(result, selectRange.value);
    return result;
  });

  // Update the slider
  selectArea.value = closestIndexes;
  // Reset the start/end calendar selection range
  if (direction === "start") endMinDate.value = e;
  else if (direction === "end") startMaxDate.value = e;
  // Recalculate the chart mask
  if (!Number.isNaN(closestIndexes[0]) && !Number.isNaN(closestIndexes[1]))
    resizeMask(chart.value);
  else return;
}

onMounted(() => {
  // Chart.js
  Chart.register(...registerables);
  createChart();
  // Slider
  selectArea.value = [0, selectRange.value];
  // Calendar
  startMinDate.value = new Date(filterTimeframe.value.x_axis.min);
  startMaxDate.value = new Date(filterTimeframe.value.x_axis.max);
  endMinDate.value = new Date(filterTimeframe.value.x_axis.min);
  endMaxDate.value = new Date(filterTimeframe.value.x_axis.max);
  // Set the calendar range to match the timeline range
  startTime.value = startMinDate.value;
  endTime.value = startMaxDate.value;
});

onBeforeUnmount(() => {
  chart.value?.destroy();
  chart.value = null;
});
</script>
