<template>
  <FlexCol innerClass="nana-calendar" align="stretch">
    <FlexRow v-if="!scrollMode" center :gap="20">
      <IconButton icon="arrow-double-left" @click="prev('year')" />
      <IconButton icon="arrow-left-bold" @click="prev('month')" />
      <Text>{{ DateUtils.formatDate(currentShowMonth, props.topMonthFormat) }}</Text>
      <IconButton icon="arrow-right-bold" @click="next('month')" />
      <IconButton icon="arrow-double-right" @click="next('year')" />
    </FlexRow>
    <Height :size="20" />
    <FlexRow position="relative">
      <FlexCol v-if="scrollMode" width="100%">
        <FlexRow innerClass="nana-calendar-days" wrap justify="space-between">
          <CalendarItem 
            v-for="weekText in props.weekTexts" 
            :key="weekText"
            :disabled="true"
            :header="true"
            :date="weekText"
            :text="weekText"
            :textProps="headerTextProps"
          />
        </FlexRow>
        <FixedVirtualList
          direction="vertical"
          :itemSize="scrollModePageHeight"
          :containerStyle="{
            width: '100%',
            height: theme.resolveSize(scrollModePageHeight * 2),
          }"
          :data="dayGridsScroll"
          :bufferSize="1"
          class="nana-calendar-scroll"
        >
          <template #item="{ item }">
            <FlexCol position="relative">
              <FlexRow justify="center">
                <Text :text="item.title" />
              </FlexRow>
              <FlexRow innerClass="nana-calendar-days" wrap justify="flex-start">
                <template
                  v-for="(day, i) in item.days"
                  :key="i"
                >
                  <view v-if="day.type === 'invisible'" style="width:14.28%"></view>
                  <CalendarItem 
                    v-else
                    :disabled="day.type === 'outOfRange'"
                    :text="day.text"
                    :textColor="day.color"
                    :textProps="{ 
                      ...dayTextProps,
                      ...day.textProps,
                    }"
                    :date="day.date"
                    :bottomText="day.bottomText?.text"
                    :bottomTextProps="day.bottomText?.textProps"
                    :topText="day.topText?.text"
                    :topTextProps="day.topText?.textProps"
                    @click="handleClick(day)"
                  />
                </template>
              </FlexRow>
              <Text
                innerClass="nana-calendar-bg-text"
                color="lightGrey"
                :fontSize="150"
                v-bind="backgroundTextProps"
              >
                {{ item.month }}
              </Text>
            </FlexCol>
          </template>
        </FixedVirtualList>
      </FlexCol>
      <template v-else>
        <FlexRow innerClass="nana-calendar-days" wrap justify="space-between">
          <CalendarItem 
            v-for="weekText in props.weekTexts" 
            :key="weekText"
            :disabled="true"
            :header="true"
            :date="weekText"
            :text="weekText"
            :textProps="headerTextProps"
          />
          <CalendarItem 
            v-for="(day, i) in dayGrids"
            :key="i"
            :disabled="day.type === 'outOfRange'"
            :text="day.text"
            :textColor="day.color"
            :textProps="{ 
              ...dayTextProps,
              ...day.textProps,
            }"
            :date="day.date"
            :bottomText="day.bottomText?.text"
            :bottomTextProps="day.bottomText?.textProps"
            :topText="day.topText?.text"
            :topTextProps="day.topText?.textProps"
            @click="handleClick(day)"
          />
        </FlexRow>
        <Text
          innerClass="nana-calendar-bg-text"
          color="lightGrey"
          :fontSize="150"
          v-bind="backgroundTextProps"
        >
          {{ currentMonth + 1 }}
        </Text>
      </template>
    </FlexRow>

  </FlexCol>
</template>

<script setup lang="ts">
import { computed, provide, ref, toRef, watch, type ComputedRef, type Ref } from 'vue';
import { useTheme } from '../theme/ThemeDefine';
import { DateUtils } from '@imengyu/imengyu-utils';
import IconButton from '../basic/IconButton.vue';
import Text from '../basic/Text.vue';
import FlexCol from '../layout/FlexCol.vue';
import FlexRow from '../layout/FlexRow.vue';
import Height from '../layout/space/Height.vue';
import CalendarItem from './CalendarItem.vue';
import type { TextProps } from '../basic/Text.vue';
import calendar, { getFestival } from './CalendarUtils';
import type { FlexProps } from '../layout/FlexView.vue';
import FixedVirtualList from '../list/FixedVirtualList.vue';

export type CalendarPickType = 'day'|'range'|'days';

export interface CalendarProps {
  /**
   * 选择的日期
   */
  modelValue?: string[] | string;
  /**
   * 进入时当前日历显示的月份
   * @default 当前月份
   */
  currentMonth?: Date;
  /**
   * 顶部月份的格式
   * @default 'yyyy-MM'
   */
  topMonthFormat?: string;
  /**
   * 可以将月份平铺滚动展示，建议只显示最近的几个月，显示过多月份易卡顿。
   * @default false
   */
  scrollMode?: boolean;
  /**
   * 滚动模式下，每页的高度
   * @default 360
   */
  scrollModePageHeight?: number;
  /**
   * 滚动模式下，滚动容器高度，即显示几页（scrollModePageHeight * scrollModePages）
   * @default 2
   */
  scrollModePages?: number;

  /**
   * 日期格式
   * @default 'yyyy-MM-dd'
   */
  dateFormat?: string;
  /**
   * 选择模式：
   * * day 选择一天
   * * range 选择范围
   * * days 选择多天
   * @default 'day'
   */
  pickType?: CalendarPickType;
  /**
   * 设置选择后在日期按钮上的状态文本，
   * 可以是一个字符串，也可以是一个数组，选中的日期按索引顺序读取文字。
   */
  pickStateText?: string|string[];
  /**
   * 限制开始日期
   * @default 5年前
   */
  startDate?: string;
  /**
   * 限制结束日期
   * @default 10年后
   */
  endDate?: string;
  /**
   * 条目的自定义属性
   */
  itemProps?: FlexProps;
  /**
   * 是否展示固定节日。
   * 数据来源于 @swjs/chinese-holidays
   * @default true
   */
  showFestival?: boolean;
  /**
   * 在日期上、下方显示自定义文字，可以用于显示节假日、事件、优惠信息等
   */
  extraDayStrings?: Record<string, {
    text: string;
    textProps?: TextProps,
    topText?: string;
    topTextProps?: TextProps,
  }>;
  /**
   * 设置不可选的日期
   */
  disabledDates?: string[];
  /**
   * 最大可选天数，仅在模式为 days, range 时生效。超出最大可选天数会触发 pickOverMaxDays 事件。为0则不限制
   * @default 0
   */
  maxPickRangeDays?: number;
  /**
   * 自定义周文字
   * @default ['日', '一', '二', '三', '四', '五', '六']
   */
  weekTexts?: string[];
  /**
   * 自定义头部日期文字的样式
   */
  headerTextProps?: TextProps,
  /**
   * 自定义日期文字的样式
   */
  dayTextProps?: TextProps,
  /**
   * 自定义单个日期文字的样式
   */
  daysTextProps?: Record<string, TextProps>,
  /**
   * 自定义背景文字的样式
   */
  backgroundTextProps?: TextProps,
}

const emit = defineEmits([ 'update:modelValue', 'pickOverMaxDays', 'selectTextChange' ]);
const props = withDefaults(defineProps<CalendarProps>(), {
  pickType: 'day',
  showFestival: true,
  maxPickRangeDays: 0,
  scrollModePageHeight: 360,
  scrollModePages: 2,
  dateFormat: 'yyyy-MM-dd',
  topMonthFormat: 'yyyy年MM月',
  currentMonth: () => new Date(),
  startDate: (props) => {
    const now = new Date();
    return DateUtils.formatDate(new Date(`${now.getFullYear() - 5}-01-01`), props.dateFormat);
  },
  endDate: (props) => {
    const now = new Date();
    return DateUtils.formatDate(new Date(`${now.getFullYear() + 10}-12-31`), props.dateFormat);
  },
  weekTexts: () => ['日', '一', '二', '三', '四', '五', '六'],
});

const currentYear = ref(props.currentMonth.getFullYear());
const currentMonth = ref(props.currentMonth.getMonth());

const startDate = computed(() => props.startDate ? new Date(props.startDate) : new Date('2000-01-01'));
const endDate = computed(() => props.endDate ? new Date(props.endDate) : new Date('2035-12-31'));

watch(() => props.currentMonth, (newVal) => {
  currentYear.value = newVal.getFullYear();
  currentMonth.value = newVal.getMonth();
});

interface DayGridInfo {
  text: string,
  color: string,
  date: string,
  bottomText?: {
    text: string,
    textProps?: TextProps,
  },
  topText?: {
    text: string,
    textProps?: TextProps,
  },
  textProps?: TextProps,
  type: 'prev'|'current'|'next'|'outOfRange'|'invisible',
}

const itemColors = computed(() => theme.resolveThemeColors({
  'CalendarItemColorNormal': 'transparent',
  'CalendarItemColorSelected': 'primary',
  'CalendarItemColorSelectedInRange': 'mask.primary',
  'CalendarItemColorDisabled': 'transparent',
  'CalendarTextColorDisabled': 'text.second',
  'CalendarTextColorNormal': 'text.content',
  'CalendarTextColorNormalWeekend': 'danger',
  'CalendarTextColorSelected': 'white',
  'CalendarTextColorSelectedInRange': 'primary',
}));
const itemSizes = computed(() => theme.resolveThemeSizes({
  'CalendarTopTextSize': 22,
  'CalendarTextSize': 30,
  'CalendarBottomTextSize': 22,
}));

const currentShowMonth = computed(() => new Date(`${currentYear.value}-${currentMonth.value + 1}-01`));
const dayGrids = computed(() => {
  const weekOfStartDay = currentShowMonth.value.getDay();
  const daysInMonth = DateUtils.getMonthDays(currentYear.value, currentMonth.value)!;

  let prevYear = currentYear.value;
  let prevMonth = currentMonth.value - 1;
  if (prevMonth < 0) {
    prevMonth = 11;
    prevYear--;
  }
  let nextYear = currentYear.value;
  let nextMonth = currentMonth.value + 1;
  if (nextMonth >= 12) {
    nextMonth = 0;
    nextYear++;
  }

  const daysPrevMonth = DateUtils.getMonthDays(prevYear, prevMonth)!;
  const result = [] as DayGridInfo[];

  //上一个月的日期
  for(let i = daysPrevMonth, j = weekOfStartDay - 1; i > 0 && j >= 0; i--, j--) {
    const date = new Date(`${prevYear}-${prevMonth + 1}-${i}`);

    result.unshift({
      text: i.toString(),
      color: itemColors.value.CalendarTextColorDisabled,
      date: DateUtils.formatDate(date, props.dateFormat),
      type: 'prev',
    });
  }

  const isRangePickerAndPickedStart = props.pickType === 'range' && getPickValueArray().length === 1;
  const rangePickerStartString = getPickValueArray()[0];
  const rangePickerStart = rangePickerStartString ? DateUtils.parseDate(rangePickerStartString) : undefined;

  //当前月的日期
  for(let i = 1; i <= daysInMonth; i++) {
    const isWeekend = (i + weekOfStartDay) % 7 === 0 || (i + weekOfStartDay - 1) % 7 === 0;

    const date = new Date(`${currentYear.value}-${currentMonth.value + 1}-${i}`);
    const lunarInfo = calendar.solar2lunar(date.getFullYear(), date.getMonth() + 1, date.getDate());
    const festival = getFestival(date);
    const dateString = DateUtils.formatDate(date, props.dateFormat);

    const item : DayGridInfo = {
      text: i.toString(),
      color: isWeekend ? itemColors.value.CalendarTextColorNormalWeekend : itemColors.value.CalendarTextColorNormal,
      date: dateString,
      type: 'current',
    };

    if (date.getTime() < startDate.value.getTime() || date.getTime() > endDate.value.getTime())
      item.type = 'outOfRange';
    else if (isRangePickerAndPickedStart && rangePickerStart) {
      if (
        (props.maxPickRangeDays > 0 && ((date.getTime() - rangePickerStart.getTime()) / 864000000) > props.maxPickRangeDays)//选择范围情况下，超出最长可选天数
        || (date < rangePickerStart)
      ) 
        item.type = 'outOfRange';
    }

    if (props.daysTextProps?.[dateString]) {
      item.textProps = props.daysTextProps[dateString];
    }
    if (props.extraDayStrings?.[dateString]) {
      item.bottomText = {
        text: props.extraDayStrings[dateString].text,
        textProps: props.extraDayStrings[dateString].textProps,
      };
      if (props.extraDayStrings[dateString].topText) {
        item.topText = {
          text: props.extraDayStrings[dateString].topText,
          textProps: props.extraDayStrings[dateString].topTextProps,
        };
      }
    } else if (props.showFestival) {  
      item.bottomText = {
        text: festival || (lunarInfo.IDayCn === '初一' ? lunarInfo.IMonthCn : lunarInfo.IDayCn) || '',
        textProps: {},
      };
    } else if (isWeekend) {
      item.color = itemColors.value.CalendarTextColorNormalWeekend;
    }

    result.push(item);
  }
  //下一个月的日期  
  for(let j = 1; j <= 7 - ((daysInMonth + weekOfStartDay) % 7); j++) {
    const date = new Date(`${nextYear}-${nextMonth + 1}-${j}`);
    result.push({
      text: j.toString(),
      color: itemColors.value.CalendarTextColorDisabled,
      date: DateUtils.formatDate(date, props.dateFormat),
      type: 'next',
    });
  }
  return result;
});
const dayGridsScroll = computed(() => {

  const startDate = DateUtils.parseDate(props.startDate);
  const endDate = DateUtils.parseDate(props.endDate);
  const endYear = endDate.getFullYear();
  const endMonth = endDate.getMonth();
  let startYear = startDate.getFullYear();
  let startMonth = startDate.getMonth();

  const resultGroup : {
    title: string,
    days: DayGridInfo[],
    month: number,
  }[] = [];

  while (true) {
    const weekOfStartDay = new Date(`${startYear}-${startMonth + 1}-1`).getDay();
    const daysInMonth = DateUtils.getMonthDays(startYear, startMonth)!;

    let prevYear = startYear;
    let prevMonth = startMonth - 1;
    if (prevMonth < 0) {
      prevMonth = 11;
      prevYear--;
    }

    const daysPrevMonth = DateUtils.getMonthDays(prevYear, prevMonth)!;

    const result = {
      title: DateUtils.formatDate(new Date(`${startYear}-${startMonth + 1}-1`), props.topMonthFormat),
      days: [] as DayGridInfo[],
      month: startMonth + 1,
    };
    
    const isRangePickerAndPickedStart = props.pickType === 'range' && getPickValueArray().length === 1;
    const rangePickerStartString = getPickValueArray()[0];
    const rangePickerStart = rangePickerStartString ? DateUtils.parseDate(rangePickerStartString) : undefined;

    //上一个月的日期
    for(let i = daysPrevMonth, j = weekOfStartDay - 1; i > 0 && j >= 0; i--, j--) {
      const date = new Date(`${prevYear}-${prevMonth + 1}-${i}`);

      result.days.unshift({
        text: i.toString(),
        color: itemColors.value.CalendarTextColorDisabled,
        date: DateUtils.formatDate(date, props.dateFormat),
        type: 'invisible',

      });
    }
    //当前月的日期
    for(let i = 1; i <= daysInMonth; i++) {
      const isWeekend = (i + weekOfStartDay) % 7 === 0 || (i + weekOfStartDay - 1) % 7 === 0;

      const date = new Date(`${startYear}-${startMonth + 1}-${i}`);
      const lunarInfo = calendar.solar2lunar(date.getFullYear(), date.getMonth() + 1, date.getDate());
      const festival = getFestival(date);
      const dateString = DateUtils.formatDate(date, props.dateFormat);

      const item : DayGridInfo = {
        text: i.toString(),
        color: isWeekend ? itemColors.value.CalendarTextColorNormalWeekend : itemColors.value.CalendarTextColorNormal,
        date: dateString,
        type: 'current',
      };

      if (date.getTime() < startDate.getTime() || date.getTime() > endDate.getTime())
        item.type = 'outOfRange';
      else if (isRangePickerAndPickedStart && rangePickerStart) {
        if (
          (props.maxPickRangeDays > 0 && ((date.getTime() - rangePickerStart.getTime()) / 86400000) > props.maxPickRangeDays)//选择范围情况下，超出最长可选天数
          || (date < rangePickerStart)
        ) 
          item.type = 'outOfRange';
      }

      if (props.daysTextProps?.[dateString]) {
        item.textProps = props.daysTextProps[dateString];
      }
      if (props.extraDayStrings?.[dateString]) {
        item.bottomText = {
          text: props.extraDayStrings[dateString].text,
          textProps: props.extraDayStrings[dateString].textProps,
        };
        if (props.extraDayStrings[dateString].topText) {
          item.topText = {
            text: props.extraDayStrings[dateString].topText,
            textProps: props.extraDayStrings[dateString].topTextProps,
          };
        }
      } else if (props.showFestival) {  
        item.bottomText = {
          text: festival || (lunarInfo.IDayCn === '初一' ? lunarInfo.IMonthCn : lunarInfo.IDayCn) || '',
          textProps: {},
        };
      } else if (isWeekend) {
        item.color = itemColors.value.CalendarTextColorNormalWeekend;
      }

      result.days.push(item);
    }
    resultGroup.push(result);

    startMonth++;
    if (startMonth > 11) {
      startMonth = 0;
      startYear++;
    }
    if (startYear > endYear)
      break;

  }
  return resultGroup;
});

function prev(type: 'month'|'year') {
  let now = new Date(currentShowMonth.value);
  if (type === 'month') {
    if (now.getMonth() === 0) {
      now.setFullYear(now.getFullYear() - 1);
      now.setMonth(11);
    } else {
      now.setMonth(now.getMonth() - 1);
    }
  } else {
    now.setFullYear(now.getFullYear() - 1);
  }
  if (now.getTime() < startDate.value.getTime())
    now = startDate.value;
  currentYear.value = now.getFullYear();
  currentMonth.value = now.getMonth();
}
function next(type: 'month'|'year') {
  let now = new Date(currentShowMonth.value);
  if (type === 'month') {
    if (now.getMonth() === 11) {
      now.setFullYear(now.getFullYear() + 1);
      now.setMonth(0);
    } else {
      now.setMonth(now.getMonth() + 1);
    }
  } else {
    now.setFullYear(now.getFullYear() + 1);
  }
  if (now.getTime() > endDate.value.getTime())
    now = endDate.value;
  currentYear.value = now.getFullYear();
  currentMonth.value = now.getMonth();
}

function getPickValueArray() {
  if (typeof props.modelValue === 'string')
    return [props.modelValue];
  return props.modelValue as string[] || [];
}
function latePickADay(dayString: string) {
  setTimeout(() => {
    const day = dayGrids.value.find(d => d.date === dayString);
    if (day)
      handleClick(day);
  }, 200);
}
function updateValue(value: string|string[]) {  
  emit('selectTextChange', typeof value === 'string' ? value : (
    value.length == 2 ? 
      `${value[0]} - ${value[1]}` :
      value.join('、')
  ));
  emit('update:modelValue', value);
}

function handleClick(day: DayGridInfo) {
  if (day.type === 'next') {
    next('month');
    latePickADay(day.date);
    return;
  }
  else if (day.type === 'prev') {
    prev('month'); 
    latePickADay(day.date);
    return;
  } else if (day.type === 'outOfRange') {
    return;
  }
  switch (props.pickType) {
    case 'day':
      updateValue(day.date);
      break;
    case 'days': {
      const index = getPickValueArray().indexOf(day.date);
      if (index !== -1)
        updateValue(getPickValueArray().filter((_, i) => i !== index));
      else {
        if (props.maxPickRangeDays > 0 && getPickValueArray().length >= props.maxPickRangeDays) {
          emit('pickOverMaxDays');
          return;
        }
        updateValue(getPickValueArray().concat([ day.date ]));
      }
      break;
    }
    case 'range':
      const len = getPickValueArray().length;
      if (len <= 0 || len >= 2) {
        updateValue([ day.date ]);
        return;
      } else if(len === 1) {
        const pickValue = getPickValueArray()[0];
        const rangePickerStart = DateUtils.parseDate(pickValue);
        if (pickValue === day.date) {
          updateValue([]);
          return;
        }
        if (DateUtils.parseDate(day.date).getTime() <= rangePickerStart.getTime()) {
          emit('pickOverMaxDays');
          return;
        }
        updateValue([ getPickValueArray()[0], day.date ]);
      }
      break;
    default:
      throw new Error('pickType not supported');
  }
}

const theme = useTheme();

export interface CalendarContext {
  itemColors: ComputedRef<Record<string, string>>,
  itemSizes: ComputedRef<Record<string, number>>,
  itemProps: Ref<FlexProps|undefined>,
  disabledDates: Ref<string[]|undefined>,
  pickType: Ref<string>,
  pickValue: Ref<string|string[]|undefined>,
  pickStateText: Ref<string|string[]|undefined>,
}
provide<CalendarContext>('calendarContext', {
  itemColors,
  itemSizes,
  itemProps: toRef(props, 'itemProps'),
  disabledDates: toRef(props, 'disabledDates'),
  pickType: toRef(props, 'pickType'),
  pickValue: toRef(props, 'modelValue'),
  pickStateText: toRef(props, 'pickStateText'),
})

defineOptions({
  options: {
    styleIsolation: 'shared',
  }
})
</script>

<style lang="scss">
.nana-calendar-bg-text {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 0;
}
.nana-calendar-days {
  position: relative;
  z-index: 1;
}
.nana-calendar-scroll {
  max-height: 70vh;
}
</style>