<template>
  <div class="relative">
    <div
      class="w-32 px-1 border border-neutral-500 cursor-pointer"
      @click="openTimeSelect = !openTimeSelect"
      :id="size"
    >
      <div v-if="totalSeconds === 0" class="text-center">
        <p>0</p>
      </div>
      <!-- This section shows the fixed time display, not the popup -->
      <div
        id="cyp-timerange-show"
        v-else
        class="flex justify-center items-center gap-1"
      >
        <p v-show="days != 0">{{ days }}d</p>
        <p v-show="hours != 0">{{ hours }}h</p>
        <p v-show="minutes != 0">{{ minutes }}m</p>
        <p v-show="seconds != 0">{{ seconds }}s</p>
      </div>
    </div>
    <!-- The following section is the popup that appears when the user clicks to open -->
    <div
      class="duration-container absolute left-0 top-full translate-y-2 dhms-input-popup-container"
      v-show="openTimeSelect"
      v-closable="{ id: size, handler: onClose }"
    >
      <div class="duration-box" v-for="(unit, index) in inputTypes" :key="unit">
        <input
          id="input_duration_dhms"
          type="text"
          class="duration duration-val input-dhms-field"
          :data-index="index"
          :data-tunit="unit"
          :data-max="tUnits[unit].max"
          :data-min="tUnits[unit].min"
          :maxlength="tUnits[unit].dsp === 'd' ? 3 : 2"
          @focus="onFocus"
          @change="onChange"
          @keyup="onKeyUp"
          v-model="inputTimeFields[index]"
        />
        <label class="duration" for="input_duration_dhms">{{
          tUnits[unit].dsp
        }}</label>
      </div>
    </div>
  </div>
</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/durationjs
 *   Duration input component with day/hour/minute/second
 *   fields and bounded min/max validation.
 */

import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import emitter from "@/utils/emitter";

const props = defineProps({
  max: {
    type: Number,
    default: 0,
    required: true,
    validator(value) {
      return value >= 0;
    },
  },
  min: {
    type: Number,
    default: 0,
    required: true,
    validator(value) {
      return value >= 0;
    },
  },
  updateMax: {
    type: Number,
    required: false,
    validator(value) {
      return value >= 0;
    },
  },
  updateMin: {
    type: Number,
    required: false,
    validator(value) {
      return value >= 0;
    },
  },
  size: {
    type: String,
    default: "md",
    required: true,
  },
  value: {
    type: Number,
    required: false,
    validator(value) {
      return value >= 0;
    },
  },
});

const emit = defineEmits(["total-seconds"]);

const display = ref("dhms");
const seconds = ref(0);
const minutes = ref(0);
const hours = ref(0);
const days = ref(0);
const maxDays = ref(0);
const minDays = ref(0);
const totalSeconds = ref(0);
const maxTotal = ref(null);
const minTotal = ref(null);
const inputTypes = ref([]);
const lastInput = ref(null);
const openTimeSelect = ref(false);

const tUnits = computed({
  get() {
    return {
      s: { dsp: "s", inc: 1, val: seconds.value, max: 59, rate: 1, min: 0 },
      m: { dsp: "m", inc: 1, val: minutes.value, max: 59, rate: 60, min: 0 },
      h: { dsp: "h", inc: 1, val: hours.value, max: 23, rate: 3600, min: 0 },
      d: {
        dsp: "d",
        inc: 1,
        val: days.value,
        max: maxDays.value,
        rate: 86400,
        min: minDays.value,
      },
    };
  },
  set(newValues) {
    for (const unit in newValues) {
      switch (unit) {
        case "s":
          seconds.value = newValues[unit].val;
          break;
        case "m":
          minutes.value = newValues[unit].val;
          break;
        case "h":
          hours.value = newValues[unit].val;
          break;
        case "d":
          days.value = newValues[unit].val;
          break;
      }
      const input = document.querySelector(`[data-tunit="${unit}"]`);
      if (input) {
        input.value = newValues[unit].val.toString();
      }
    }
  },
});

const inputTimeFields = computed(() => {
  const paddedTimeFields = [];
  inputTypes.value.forEach((inputTypeUnit) => {
    paddedTimeFields.push(
      tUnits.value[inputTypeUnit].val.toString().padStart(2, "0"),
    );
  });
  return paddedTimeFields;
});

/** Closes the time selection dropdown. */
function onClose() {
  openTimeSelect.value = false;
}

/**
 * Selects all text in the focused input field.
 * @param {FocusEvent} event - The focus event.
 */
function onFocus(event) {
  lastInput.value = event.target;
  lastInput.value.select();
}

/**
 * Validates and updates the duration value when an input changes.
 * @param {Event} event - The change event from a duration input.
 */
function onChange(event) {
  let decoratedInputValue;
  if (Number.isNaN(Number(event.target.value))) {
    event.target.value = "00";
  } else {
    event.target.value = event.target.value.toString();
  }
  decoratedInputValue = event.target.value.toString();

  const inputValue = Number.parseInt(event.target.value, 10);
  const max = Number.parseInt(event.target.dataset.max, 10);
  const min = Number.parseInt(event.target.dataset.min, 10);
  if (inputValue > max) {
    decoratedInputValue = max.toString().padStart(2, "0");
  } else if (inputValue < min) {
    decoratedInputValue = min.toString().padStart(2, "0");
  }
  const dsp = event.target.dataset.tunit;
  tUnits.value[dsp].val = decoratedInputValue;
  switch (dsp) {
    case "d":
      days.value = decoratedInputValue;
      break;
    case "h":
      hours.value = decoratedInputValue;
      break;
    case "m":
      minutes.value = decoratedInputValue;
      break;
    case "s":
      seconds.value = decoratedInputValue;
      break;
  }

  calculateTotalSeconds();
}

/**
 * Handles arrow key up/down to increment/decrement duration values.
 * @param {KeyboardEvent} event - The keyup event.
 */
function onKeyUp(event) {
  event.target.value = event.target.value.replaceAll(/\D/g, "");
  if (event.keyCode === 38 || event.keyCode === 40) {
    actionUpDown(event.target, event.keyCode === 38, true);
  }
}

/**
 * Increments or decrements a duration unit value with carry-over.
 * @param {HTMLInputElement} input - The input element to update.
 * @param {boolean} goUp - True to increment, false to decrement.
 * @param {boolean} [selectIt=false] - Whether to select the input text after.
 */
function actionUpDown(input, goUp, selectIt = false) {
  const tUnit = input.dataset.tunit;
  let newVal = getNewValue(input);

  if (goUp) {
    newVal = handleArrowUp(newVal, tUnit, input);
  } else {
    newVal = handleArrowDown(newVal, tUnit);
  }

  updateInputValue(input, newVal, tUnit);
  if (selectIt) {
    input.select();
  }
  calculateTotalSeconds();
}

function getNewValue(input) {
  const newVal = Number.parseInt(input.value, 10);
  return Number.isNaN(newVal) ? 0 : newVal;
}

function handleArrowUp(newVal, tUnit, input) {
  newVal += tUnits.value[tUnit].inc;
  if (newVal > tUnits.value[tUnit].max) {
    if (tUnits.value[tUnit].dsp === "d") {
      totalSeconds.value = maxTotal.value;
    } else {
      newVal = newVal % (tUnits.value[tUnit].max + 1);
      incrementPreviousUnit(input);
    }
  }
  return newVal;
}

function handleArrowDown(newVal, tUnit) {
  newVal -= tUnits.value[tUnit].inc;
  if (newVal < 0) {
    newVal = tUnits.value[tUnit].max + 1 - tUnits.value[tUnit].inc;
  }
  return newVal;
}

function incrementPreviousUnit(input) {
  if (input.dataset.index > 0) {
    const prevUnit = document.querySelector(
      `input[data-index="${Number.parseInt(input.dataset.index, 10) - 1}"]`,
    );
    if (prevUnit) actionUpDown(prevUnit, true);
  }
}

function updateInputValue(input, newVal, tUnit) {
  input.value = newVal.toString();
  switch (tUnit) {
    case "d":
      days.value = input.value;
      break;
    case "h":
      hours.value = input.value;
      break;
    case "m":
      minutes.value = input.value;
      break;
    case "s":
      seconds.value = input.value;
      break;
  }
}

/**
 * Converts total seconds to day/hour/minute/second components.
 * @param {number} totalSec - The total seconds to convert.
 * @param {string} [size] - 'max' or 'min' to set boundary days.
 */
function secondToDate(totalSec, size) {
  totalSec = Number.parseInt(totalSec, 10);
  if (!Number.isNaN(totalSec)) {
    seconds.value = totalSec % 60;
    minutes.value = (Math.floor(totalSec - seconds.value) / 60) % 60;
    hours.value = Math.floor(totalSec / 3600) % 24;
    days.value = Math.floor(totalSec / (3600 * 24));

    if (size === "max") {
      maxDays.value = Math.floor(totalSec / (3600 * 24));
    } else if (size === "min") {
      minDays.value = Math.floor(totalSec / (3600 * 24));
    }
  }
}

/** Calculates total seconds from all duration units and emits the result. */
function calculateTotalSeconds() {
  let total = 0;

  for (const unit in tUnits.value) {
    const val = Number.parseInt(tUnits.value[unit].val, 10);
    if (!Number.isNaN(val)) {
      total += val * tUnits.value[unit].rate;
    }
  }

  if (total >= maxTotal.value) {
    total = maxTotal.value;
    secondToDate(maxTotal.value, "max");
  } else if (total <= minTotal.value) {
    total = minTotal.value;
    secondToDate(minTotal.value, "min");
  } else if (props.size === "min" && total <= maxTotal.value) {
    maxDays.value = Math.floor(maxTotal.value / (3600 * 24));
  }
  totalSeconds.value = total;
  emit("total-seconds", total);
}

/** Initializes the duration display based on min/max boundaries and preset value. */
function createData() {
  const size = props.size;

  if (maxTotal.value !== null && minTotal.value !== null) {
    switch (size) {
      case "max":
        secondToDate(minTotal.value, "min");
        secondToDate(maxTotal.value, "max");
        totalSeconds.value = maxTotal.value;
        if (props.value !== null) {
          totalSeconds.value = props.value;
          secondToDate(props.value);
        }
        break;
      case "min":
        secondToDate(maxTotal.value, "max");
        secondToDate(minTotal.value, "min");
        totalSeconds.value = minTotal.value;
        if (props.value !== null) {
          totalSeconds.value = props.value;
          secondToDate(props.value);
        }
        break;
    }
  }
}

// Sync maxTotal/minTotal from props so calculateTotalSeconds can clamp
// and createData can initialize the display.
watch(
  () => [props.max, props.updateMax],
  ([max, updateMax]) => {
    maxTotal.value = updateMax ?? max;
    createData();
  },
  { immediate: true },
);
watch(
  () => [props.min, props.updateMin],
  ([min, updateMin]) => {
    minTotal.value = updateMin ?? min;
    createData();
  },
  { immediate: true },
);

// created
emitter.on("reset", () => {
  createData();
});

onBeforeUnmount(() => {
  emitter.off("reset");
});

// mounted
onMounted(() => {
  inputTypes.value = display.value.split("");
});

const vClosable = {
  mounted(el, { value }) {
    const handleOutsideClick = function (e) {
      let target = e.target;
      while (target && target.id !== value.id) {
        target = target.parentElement;
      }
      const isClickOutside = target?.id !== value.id && !el.contains(e.target);
      if (isClickOutside) {
        value.handler();
      }
      e.stopPropagation();
    };
    document.addEventListener("click", handleOutsideClick);
    el._handleOutsideClick = handleOutsideClick;
  },
  unmounted(el) {
    document.removeEventListener("click", el._handleOutsideClick);
  },
};
</script>
<style scoped>
.duration-container {
  margin: 4px;
  border-radius: 4px;
  background: var(--10, #fff);
  box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.25);
  height: 36px;
  width: 221px;
}
.duration-box {
  float: left;
  overflow: auto;
  height: var(--main-input-height);
  padding: 4px;
}
.duration-box > .duration {
  border: 1px solid var(--main-bg-light);
  border-right: 0;
  border-left: 0;
  background-color: transparent;
  color: var(--main-bg-light);
}
.duration-box > .duration:nth-child(1) {
  border-left: 1px solid var(--main-bg-light);
  border-top-left-radius: var(--main-input-br);
  border-bottom-left-radius: var(--main-input-br);
}
.duration-box > .duration:nth-last-child(1) {
  border-right: 1px solid var(--main-bg-light);
  border-top-right-radius: var(--main-input-br);
  border-bottom-right-radius: var(--main-input-br);
}
.duration {
  float: left;
  display: block;
  overflow: auto;
  height: var(--main-input-height);
  outline: none;
  font-size: 14px;
  margin: 0px 2px;
}
.duration-box > label.duration {
  line-height: 28px;
  width: 12px;
  overflow: hidden;
}
.duration-box > input[type="text"].duration {
  text-align: right;
  width: 26px;
  padding: 3px 2px 0px 0px;
}
.duration-box > input[type="button"].duration {
  width: 60px;
  cursor: pointer;
}
</style>
