<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import dayjs from 'dayjs';
import { Calendar } from '@element-plus/icons-vue';

defineOptions({ name: 'BusinessDateRangePicker' });

type DateRangeValue = [string, string];

interface DateRangeShortcut {
  label: string;
  value: DateRangeValue | (() => DateRangeValue);
}

interface Props {
  placeholder?: string;
  disabled?: boolean;
  shortcuts?: DateRangeShortcut[];
  popoverWidth?: number;
}

const props = withDefaults(defineProps<Props>(), {
  placeholder: '请选择日期范围',
  disabled: false,
  shortcuts: () => [],
  popoverWidth: 458
});

const model = defineModel<DateRangeValue>({
  default: () => ['', '']
});

const popoverVisible = ref(false);
const activeTab = ref<'advanced' | 'custom'>('custom');
const draftRange = ref<DateRangeValue>(normalizeDateRange(model.value));
const panelMonth = ref(dayjs().startOf('month'));

const displayText = computed(() => {
  const normalizedRange = normalizeDateRange(model.value);

  return normalizedRange.every(Boolean) ? normalizedRange.join(' ~ ') : '';
});

const confirmDisabled = computed(() => {
  return !isCompleteDateRange(draftRange.value);
});

const defaultShortcuts = computed<DateRangeShortcut[]>(() => [
  {
    label: '最近 7 天',
    value: () => buildRecentDateRange(7)
  },
  {
    label: '最近 30 天',
    value: () => buildRecentDateRange(30)
  },
  {
    label: '本周',
    value: () => [dayjs().startOf('week').format('YYYY-MM-DD'), dayjs().endOf('week').format('YYYY-MM-DD')]
  },
  {
    label: '本月',
    value: () => [dayjs().startOf('month').format('YYYY-MM-DD'), dayjs().endOf('month').format('YYYY-MM-DD')]
  }
]);

const resolvedShortcuts = computed(() => (props.shortcuts?.length ? props.shortcuts : defaultShortcuts.value));

const panelTitle = computed(() => panelMonth.value.format('YYYY 年 M 月'));

const calendarCells = computed(() => {
  const startDate = panelMonth.value.startOf('month').startOf('week');

  return Array.from({ length: 42 }, (_, index) => {
    const date = startDate.add(index, 'day');
    const dateText = date.format('YYYY-MM-DD');

    return {
      date,
      dateText,
      dayText: date.format('D'),
      isCurrentMonth: date.month() === panelMonth.value.month(),
      isSelected: isSelectedDate(dateText),
      isInRange: isInSelectedRange(dateText),
      isStart: draftRange.value[0] === dateText,
      isEnd: draftRange.value[1] === dateText
    };
  });
});

const activeShortcutLabel = computed(() => {
  const matchedShortcut = resolvedShortcuts.value.find(shortcut => {
    const shortcutRange = resolveShortcutValue(shortcut);

    return shortcutRange[0] === draftRange.value[0] && shortcutRange[1] === draftRange.value[1];
  });

  return matchedShortcut?.label || '';
});

function buildRecentDateRange(days: number): DateRangeValue {
  const end = dayjs();
  const start = dayjs().subtract(Math.max(days - 1, 0), 'day');

  return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')];
}

function normalizeDateRange(value: readonly string[] | null | undefined): DateRangeValue {
  const [startDate = '', endDate = ''] = value || [];

  return [formatDate(startDate), formatDate(endDate)];
}

function formatDate(value: string) {
  if (!value) {
    return '';
  }

  const parsed = dayjs(value);

  return parsed.isValid() ? parsed.format('YYYY-MM-DD') : '';
}

function isCompleteDateRange(value: readonly string[]) {
  return value.length === 2 && value.every(item => dayjs(item).isValid());
}

function syncPanelMonth(value: readonly string[]) {
  const [startDate, endDate] = value;
  const candidateDate = startDate || endDate;
  const parsed = dayjs(candidateDate);

  panelMonth.value = parsed.isValid() ? parsed.startOf('month') : dayjs().startOf('month');
}

function isSelectedDate(dateText: string) {
  return draftRange.value.includes(dateText);
}

function isInSelectedRange(dateText: string) {
  if (!isCompleteDateRange(draftRange.value)) {
    return false;
  }

  const current = dayjs(dateText);
  const startDate = dayjs(draftRange.value[0]);
  const endDate = dayjs(draftRange.value[1]);

  return current.isAfter(startDate, 'day') && current.isBefore(endDate, 'day');
}

function resolveShortcutValue(shortcut: DateRangeShortcut) {
  return normalizeDateRange(typeof shortcut.value === 'function' ? shortcut.value() : shortcut.value);
}

function updateModel(value: DateRangeValue) {
  const normalizedRange = normalizeDateRange(value);

  if (!isCompleteDateRange(normalizedRange)) {
    return;
  }

  model.value = normalizedRange;
}

function handleVisibleChange(currentVisible: boolean) {
  popoverVisible.value = currentVisible;

  if (currentVisible) {
    draftRange.value = normalizeDateRange(model.value);
    syncPanelMonth(draftRange.value);
    return;
  }

  draftRange.value = normalizeDateRange(model.value);
}

function handleShortcutClick(shortcut: DateRangeShortcut) {
  const shortcutRange = resolveShortcutValue(shortcut);

  draftRange.value = shortcutRange;
  syncPanelMonth(shortcutRange);
}

function handleDateClick(dateText: string) {
  const [startDate, endDate] = draftRange.value;

  if (!startDate || (startDate && endDate)) {
    draftRange.value = [dateText, ''];
    return;
  }

  if (dayjs(dateText).isBefore(dayjs(startDate), 'day')) {
    draftRange.value = [dateText, startDate];
    return;
  }

  draftRange.value = [startDate, dateText];
}

function switchPanelMonth(step: number) {
  panelMonth.value = panelMonth.value.add(step, 'month');
}

function switchPanelYear(step: number) {
  panelMonth.value = panelMonth.value.add(step, 'year');
}

function handleConfirm() {
  if (confirmDisabled.value) {
    return;
  }

  updateModel(draftRange.value);
  popoverVisible.value = false;
}

function handleCancel() {
  draftRange.value = normalizeDateRange(model.value);
  popoverVisible.value = false;
}

watch(
  () => model.value,
  value => {
    if (!popoverVisible.value) {
      draftRange.value = normalizeDateRange(value);
    }
  }
);
</script>

<template>
  <ElPopover
    :visible="popoverVisible"
    trigger="click"
    placement="bottom-start"
    :width="popoverWidth"
    popper-class="business-date-range-picker__popper"
    :disabled="disabled"
    @update:visible="handleVisibleChange"
  >
    <template #reference>
      <ElInput
        :model-value="displayText"
        readonly
        :disabled="disabled"
        :placeholder="placeholder"
        class="business-date-range-picker__input"
      >
        <template #suffix>
          <ElIcon><Calendar /></ElIcon>
        </template>
      </ElInput>
    </template>

    <div class="business-date-range-picker__panel">
      <aside class="business-date-range-picker__shortcuts">
        <ElButton
          v-for="shortcut in resolvedShortcuts"
          :key="shortcut.label"
          :type="activeShortcutLabel === shortcut.label ? 'primary' : 'default'"
          class="business-date-range-picker__shortcut"
          @click="handleShortcutClick(shortcut)"
        >
          {{ shortcut.label }}
        </ElButton>
      </aside>

      <section class="business-date-range-picker__main">
        <div class="business-date-range-picker__tabs">
          <button
            class="business-date-range-picker__tab"
            :class="{ 'business-date-range-picker__tab--active': activeTab === 'advanced' }"
            type="button"
            @click="activeTab = 'advanced'"
          >
            高级选项
          </button>
          <button
            class="business-date-range-picker__tab"
            :class="{ 'business-date-range-picker__tab--active': activeTab === 'custom' }"
            type="button"
            @click="activeTab = 'custom'"
          >
            自定义
          </button>
        </div>

        <div v-if="activeTab === 'advanced'" class="business-date-range-picker__advanced">
          <ElButton
            v-for="shortcut in resolvedShortcuts"
            :key="shortcut.label"
            plain
            :type="activeShortcutLabel === shortcut.label ? 'primary' : 'default'"
            class="business-date-range-picker__advanced-button"
            @click="handleShortcutClick(shortcut)"
          >
            {{ shortcut.label }}
          </ElButton>
        </div>

        <div v-else class="business-date-range-picker__custom">
          <div class="business-date-range-picker__fields">
            <ElInput v-model="draftRange[0]" class="business-date-range-picker__field" />
            <span class="business-date-range-picker__separator">—</span>
            <ElInput v-model="draftRange[1]" class="business-date-range-picker__field" />
          </div>

          <div class="business-date-range-picker__calendar">
            <div class="business-date-range-picker__calendar-header">
              <button type="button" class="business-date-range-picker__icon-button" @click="switchPanelYear(-1)">
                «
              </button>
              <button type="button" class="business-date-range-picker__icon-button" @click="switchPanelMonth(-1)">
                ‹
              </button>
              <span class="business-date-range-picker__calendar-title">{{ panelTitle }}</span>
              <button type="button" class="business-date-range-picker__icon-button" @click="switchPanelMonth(1)">
                ›
              </button>
              <button type="button" class="business-date-range-picker__icon-button" @click="switchPanelYear(1)">
                »
              </button>
            </div>

            <div class="business-date-range-picker__weekdays">
              <span>日</span>
              <span>一</span>
              <span>二</span>
              <span>三</span>
              <span>四</span>
              <span>五</span>
              <span>六</span>
            </div>

            <div class="business-date-range-picker__days">
              <button
                v-for="cell in calendarCells"
                :key="cell.dateText"
                type="button"
                class="business-date-range-picker__day"
                :class="{
                  'business-date-range-picker__day--muted': !cell.isCurrentMonth,
                  'business-date-range-picker__day--selected': cell.isSelected,
                  'business-date-range-picker__day--in-range': cell.isInRange,
                  'business-date-range-picker__day--start': cell.isStart,
                  'business-date-range-picker__day--end': cell.isEnd
                }"
                @click="handleDateClick(cell.dateText)"
              >
                <span>{{ cell.dayText }}</span>
              </button>
            </div>
          </div>
        </div>

        <div class="business-date-range-picker__footer">
          <ElButton @click="handleCancel">取消</ElButton>
          <ElButton type="primary" :disabled="confirmDisabled" @click="handleConfirm">确定</ElButton>
        </div>
      </section>
    </div>
  </ElPopover>
</template>

<style scoped>
.business-date-range-picker__input {
  width: 100%;
}

.business-date-range-picker__panel {
  display: grid;
  grid-template-columns: 102px minmax(0, 1fr);
  overflow: hidden;
  border-radius: 8px;
  background-color: var(--el-bg-color);
}

.business-date-range-picker__shortcuts {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 10px;
  border-right: 1px solid var(--el-border-color-lighter);
}

.business-date-range-picker__shortcut {
  width: 78px;
  margin-left: 0;
}

.business-date-range-picker__main {
  min-width: 0;
}

.business-date-range-picker__tabs {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 12px;
  border-bottom: 1px solid var(--el-border-color-lighter);
}

.business-date-range-picker__tab {
  position: relative;
  height: 40px;
  padding: 0 12px;
  border: none;
  background: transparent;
  color: var(--el-text-color-regular);
  cursor: pointer;
  font-size: 14px;
}

.business-date-range-picker__tab--active {
  color: var(--el-color-primary);
}

.business-date-range-picker__tab--active::after {
  position: absolute;
  right: 12px;
  bottom: 0;
  left: 12px;
  height: 2px;
  background-color: var(--el-color-primary);
  content: '';
}

.business-date-range-picker__advanced {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  min-height: 230px;
  padding: 16px;
}

.business-date-range-picker__advanced-button {
  margin-left: 0;
}

.business-date-range-picker__custom {
  padding: 10px 8px 0;
}

.business-date-range-picker__fields {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20px minmax(0, 1fr);
  align-items: center;
  gap: 6px;
  padding: 0 8px 6px;
}

.business-date-range-picker__separator {
  color: var(--el-text-color-placeholder);
  text-align: center;
}

.business-date-range-picker__footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--el-border-color-light);
}

:global(.business-date-range-picker__popper.el-popover.el-popper) {
  padding: 0;
  border: 1px solid var(--el-border-color-light);
  box-shadow: var(--el-box-shadow-light);
}

.business-date-range-picker__calendar {
  padding: 0 10px 8px;
}

.business-date-range-picker__calendar-header {
  display: grid;
  grid-template-columns: 28px 28px minmax(0, 1fr) 28px 28px;
  align-items: center;
  height: 36px;
  color: var(--el-text-color-primary);
}

.business-date-range-picker__calendar-title {
  text-align: center;
  font-size: 16px;
}

.business-date-range-picker__icon-button {
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--el-text-color-placeholder);
  cursor: pointer;
  font-size: 18px;
  line-height: 28px;
}

.business-date-range-picker__icon-button:hover {
  color: var(--el-color-primary);
  background-color: var(--el-fill-color-light);
}

.business-date-range-picker__weekdays,
.business-date-range-picker__days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.business-date-range-picker__weekdays {
  height: 30px;
  align-items: center;
  border-bottom: 1px solid var(--el-border-color-lighter);
  color: var(--el-text-color-regular);
  font-size: 13px;
  text-align: center;
}

.business-date-range-picker__days {
  padding-top: 4px;
}

.business-date-range-picker__day {
  height: 30px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--el-text-color-regular);
  cursor: pointer;
  font-size: 13px;
}

.business-date-range-picker__day span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
}

.business-date-range-picker__day:hover span {
  color: var(--el-color-primary);
}

.business-date-range-picker__day--muted {
  color: var(--el-text-color-placeholder);
}

.business-date-range-picker__day--in-range {
  background-color: var(--el-color-primary-light-9);
}

.business-date-range-picker__day--selected span {
  background-color: var(--el-color-primary);
  color: #fff;
}

.business-date-range-picker__day--start {
  border-radius: 6px 0 0 6px;
}

.business-date-range-picker__day--end {
  border-radius: 0 6px 6px 0;
}
</style>
