<i18n>
{
  "ru": {
    "SlotsCalendar": {
      "daysOfWeek": {
        "0": "Вс",
        "1": "Пн",
        "2": "Вт",
        "3": "Ср",
        "4": "Чт",
        "5": "Пт",
        "6": "Сб"
      },
      "buttons": {
        "ok": "Применить",
        "cancel": "Отменить"
      },
      "downloadTickets": "Выгрузить записи на прием",
      "ThereSNoTickets": "Пока нет записей на прием для выгрузки",
      "downloadError": "Не удалось загрузить таблицу слотов",
      "statementHasNoFound": "Не удвлось найти заявление",
      "goToTheStatement": "Перейти к заявлению",
      "statement": "Заявление",
      "snils": "СНИЛС",
      "markAsServed": "Пометить обслуженным",
      "markAsBooked": "Пометить забронированным",
      "cancelBooking": "Отменить бронирование",
      "deleteAllSlots": "Удалить все слоты",
      "deleteSlot": "Удалить слот",
      "cancelAllBookings": "Отменить все бронирования",
      "status": {
        "101": "Зарезервировано",
        "201": "Забронировано",
        "401": "Обслужено",
        "301": "Отменено"
      },
      "plural": {
        "slot": "Нет свободных слотов | {n} слот | {n} слота | {n} слотов"
      },
      "labels": {
        "fullName": "Посетитель",
        "caseNumber": "Заявление",
        "actions": "Действия"
      }
    }
  },
  "en": {
    "SlotsCalendar": {
      "daysOfWeek": {
        "0": "Su",
        "1": "Mo",
        "2": "Tu",
        "3": "We",
        "4": "Th",
        "5": "Fr",
        "6": "St"
      },
      "buttons": {
        "ok": "Ok",
        "cancel": "Cancel"
      },
      "downloadTickets": "Download tickets",
      "ThereSNoTickets": "There's no tickets yet",
      "downloadError": "Failed to download slots table!",
      "statementHasNoFound": "Statement has no found",
      "goToTheStatement": "Go to the statement",
      "statement": "Statement id",
      "snils": "SNILS",
      "markAsServed": "Mark as served",
      "markAsBooked": "Mark as booked",
      "cancelBooking": "Cancel booking",
      "cancelAllBookings": "Cancel all bookings",
      "deleteAllSlots": "Delete slots",
      "deleteSlot": "Delete slot",
      "status": {
        "101": "Reserved",
        "201": "Booked",
        "401": "Served",
        "301": "Cancelled"
      },
      "plural": {
        "slot": "No slots | {n} slot | {n} slots"
      }
    }
  }
}
</i18n>

<template>
  <div class="slots-calendar column q-pa-md">
    <div class="q-gutter-y-md col q-mb-sm">
      <div class="row justify-center items-center">
        <div class="row justify-center items-center">
          <q-btn color="grey-7" flat icon="keyboard_arrow_left" @click="prevWeek" />
          <q-btn color="grey-7" flat>
            {{ displayDate(dates[0]) }} &mdash; {{ displayDate(dates[6]) }}
            <q-popup-proxy ref="dateRangeProxy" transition-show="scale" transition-hide="scale">
              <q-date
                minimal
                mask="DD.MM.YYYY"
                :value="[{from: displayDate(dates[0]), to: displayDate(dates[6])}]"
                @input="setWeek"
                v-close-popup
              />
            </q-popup-proxy>
          </q-btn>
          <q-btn color="grey-7" flat icon="keyboard_arrow_right" @click="nextWeek" />
        </div>
      </div>
    </div>
    <div class="justify-center"></div>
    <div class="slots-calendar__container">
      <div class="slots-calendar__row slots-calendar__header row full-width justify-center">
        <div
          v-for="day in dates"
          :key="day.getTime()"
          :class="[
            'slots-calendar__day',
            'col-auto',
            'text-center',
            'q-py-sm',
            getDayColor(day),
          ]"
          :title="getDayDescription(day)"
        >
          <div class="text-weight-bold">{{ $t(`SlotsCalendar.daysOfWeek.${day.getDay()}`) }}</div>
          <div>{{ day.getDate() }}.{{ String(day.getMonth() + 1).padStart(2, '0') }}</div>
        </div>
      </div>
      <div class="slots-calendar__row row full-width justify-center">
        <div
          v-for="(slotsCol, index) in calendarGrid"
          :key="`slots-col-${index}`"
          class="slots-calendar__day col-auto text-center bg-grey-1"
        >
          <div
            v-for="slot in slotsCol"
            :key="slot.date + slot.hours + slot.minutes"
            :class="[
              'slots-calendar__time-slot',
              {
                'slots-calendar__time-slot--past': slot.isPast,
                'slots-calendar__time-slot--has-slots': slot.slots.length,
                'slots-calendar__time-slot--selected': 
                  slot.slots && slot.slots.length &&
                    reservationSlot && (slot.slots[0].slotId === reservationSlot.slotId)
              }
            ]"
            :title="$tc('SlotsCalendar.plural.slot', slot.slots.length)"
            @click="selectSlot(slot)"
          >
            <div class="slots-calendar__time-text">
              {{ slot.hours }}:{{ slot.minutes }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { OrgScheduleDTO, SlotDTO, TicketDTO, ReservedTicketDTO } from '@openapi/preorder-pgs';
import { convertDateFormat, isPastDate, getCurrentDate, } from '@utils/format-date';
import { DateFormat } from '@constants/utils/format-date';
import preorderService from '@services/preorder';
import isNil from 'lodash/isNil';
import isEmpty from 'lodash/isEmpty';
import some from 'lodash/some';
import get from 'lodash/get';
import identity from 'lodash/identity';
import { Organization, Service } from '@openapi/registry-service';
import { shiftDateTo, getDaysInMs, parseDateTime, isDateValid, parseTimezone, timeParse, timeAdd, timeFormat } from '@components/tickets/utils/date-manipulations';
import { DocumentApi, Document } from '@openapi/document-registry';
import { notifyError, notifyInfo } from '@utils/notify';
import { RestDay } from '@/openapi/idm';
import { WorkCalendarForDomain } from '../utils/WorkCalendarForDomain';
import { th } from 'date-fns/locale';
import { TableColumn } from '@/interfaces';

const documentApi = new DocumentApi();

interface Slot extends Omit<SlotDTO, 'visitTime' | 'endVisitTime'> {
  visitTime: string;
  endVisitTime: string;
}

interface Statement {
  fullName: string;
  shortName: string;
  startDate: string;
  applicant: {
    fullName: string;
  }
}

interface GridSlot {
  hours: string;
  minutes: string;
  slots: SlotDTO[];
  tickets: TicketDTO[];
  ticketsByStatus: Record<string, TicketDTO[]>;
  date: string;
  isPast: boolean;
  isWorkDay: boolean;
}

interface ExtendedOrganization extends Organization {
  ogrn: string;
  regionId: string;
  services: Service[]
  parent: {
    timezone: string;
  };
}

enum TICKET_STATUS {
  'reserved' = '101',
  'booked' = '201',
  'served' = '401',
  'cancelled' = '301',
}

const TICKET_STATUS_COLORS: Record<string, string> = {
  '101': 'yellow-8',
  '201': 'green-6',
  '401': 'primary-6',
  '301': 'grey-6',
};

@Component({
  components: {},
  methods: {
    get,
  }
})
export default class SlotsCalendar extends Vue {
  // Импорт типа функции в класс
  get: typeof get;

  @Prop({default: () => ({})}) organization: ExtendedOrganization;
  @Prop({default: () => ([])}) schedule: OrgScheduleDTO;
  @Prop({default: () => ({})}) workCalendar: WorkCalendarForDomain;
  @Prop({default: () => ({})}) service: Service;
  @Prop({default: () => ({})}) selected: ReservedTicketDTO;

  public selectedDate = convertDateFormat(new Date(), '', DateFormat.date);
  public slots: SlotDTO[] = [];
  public tickets: TicketDTO[] = [];

  public beginDate: Date = new Date();
  public endDate: Date = shiftDateTo(new Date(), 7);

  public selectedStatements: Record<string, Document> = {};

  @Watch('schedule', { immediate: true, deep: true })
  private onScheduleChange(schedule: OrgScheduleDTO): void {
    const index = schedule.services?.reduce((index, itm) => {
      if (itm.serviceId) {
        index[String(itm.serviceId)] = true
      }
      return index;
    }, ({} as Record<string, boolean>));
    if (!index) return;
    this.fetch(this.organization.ogrn, this.service.targetId, this.schedule?.areaId);
  }

  @Watch('service', { immediate: true })
  onServiceChange(service?: Service): void {
    if (isNil(service)) {
      return;
    }
    this.fetch(this.organization.ogrn, service.targetId, this.schedule?.areaId);
  }

  public created(): void {
    this.calcDateRange(new Date(getCurrentDate()));
  }

  get reservationSlot() {
    return this.selected?.slots?.[0] || this.selected?.timeSlot;
  }

  get isBooked(): boolean {
    return !!this.selected?.reservedUntil;
  }

  dates: Date[] = [];

  public calcDateRange(date: Date): void {
    const days = date.getDay() || 7;
    const begin = date.getTime() - getDaysInMs(days - 1);
    const end = date.getTime() + getDaysInMs(7 - days);
    this.beginDate = new Date(begin);
    this.endDate = new Date(end);

    const dates = [] as Date[];
    for (let day = 0; day < 7; day++) {
      dates.push(shiftDateTo(begin, day));
    }
    this.dates = dates;
  }

  getTimezone(): string {
    return parseTimezone(this.organization.parent.timezone);
    // return parseTimezone("+03:00");
  }

  public get slotsIndex(): Record<string, SlotDTO[]> {
    const index = {} as {[key: string]: SlotDTO[]}

    return this.slots.reduce((index, slot) => {
      
      const visitTime = parseDateTime(slot.visitTime || '');
      const endVisitTime = parseDateTime(slot.visitTime || '');

      const indexKey = slot.visitTime?.replace(/([-T:]|\+.*)/g, '') || '';
      if (!index[indexKey]) index[indexKey] = [];
      index[indexKey].push(slot);

      return index;
    }, index);
  }

  private statrOfDay = timeParse('09:00:00');
  private endOfDay = timeParse('24:00:00');

  get startOfWorkDay(): number {
    const seconds = this.schedule?.schedules?.reduce((time: number, day) => {
      if (day.startTime) {
        return Math.min(time, timeParse(day.startTime));
      }
      return time;
    }, this.statrOfDay) || NaN;
    return seconds;
  }

  get endOfWorkDay(): number {
    const seconds = this.schedule?.schedules?.reduce((time: number, day) => {
      if (day.endTime) {
        return Math.max(time, timeParse(day.endTime));
      }
      return time;
    }, 0) || NaN;
    return seconds;
  }

  get serviceTargetId(): string | undefined {
    return this.service?.targetId;
  }

  public get ticketsIndex(): Record<string, TicketDTO[]> {
    const index = {} as {[key: string]: TicketDTO[]}
    return this.tickets.reduce((index, ticket) => {
      const indexKey = ticket.timeSlot?.visitTime?.replace(/([-T:]|\+.*)/g, '') || '';
      if (!index[indexKey]) index[indexKey] = [];
      index[indexKey].push(ticket);
      return index;
    }, index)
  }

  public get calendarGrid(): GridSlot[][] {
    const timeSlot =  this.schedule.timeSlot || 0;
    const numberOfSlots = ((this.endOfWorkDay - this.startOfWorkDay) / 60) / timeSlot;
    const shift = this.startOfWorkDay / 60;
    let time = 0;

    const grid = [] as GridSlot[][];

    for (let day = 0; day < 7; day++) {
      const date = this.dates[day];
      const DD = String(date.getDate()).padStart(2, '0');
      const YYYY = String(date.getFullYear()).padStart(2, '0');
      const MM = String(date.getMonth() + 1).padStart(2, '0');

      for (let i = 0; i < numberOfSlots; i++) {
        time = shift + timeSlot * i;
        const HH = String(Math.trunc(time / 60)).padStart(2, '0');
        const mm = String(time % 60).padStart(2, '0');
        const indexKey = `${YYYY}${MM}${DD}${HH}${mm}00`;

        const slots = this.slotsIndex[indexKey] || [];
        const tickets = this.ticketsIndex[indexKey] || [];
        const ticketsByStatus = SlotsCalendar.splitTickets(tickets);

        const isWorkDay = !!this.workCalendar.getDay(date).workDay;
        const isPast = isPastDate(new Date(`${YYYY}-${MM}-${DD} ${HH}:${mm}:00${this.getTimezone()}`));

        if (!grid[day]) grid[day] = [];
        grid[day].push({
          hours: HH,
          minutes: mm,
          date: `${DD}.${MM}.${YYYY}`,
          slots,
          tickets,
          ticketsByStatus,
          isPast,
          isWorkDay,
        });
      }
    }
    return grid;
  }

  areSlotsBooked(slotIds: string[]): boolean {
    return some(this.tickets, ({ slotId, timeSlot, status }) => {
      slotId = slotId || timeSlot?.slotId;
      const booked = !!slotId && slotIds.includes(slotId) && status?.statusCode === '201';
      return booked;
    });
  }

  getDayIndex(date: Date): number {
    const day = date.getDay();
    if (day === 0) return 6;
    return day - 1;
  }

  getTicketsOfDay(date: Date): TicketDTO[] {
    return this.calendarGrid[this.getDayIndex(date)].flatMap(
      ({ tickets }) => tickets
    );
  }

  getSlotsOfDay(date: Date): SlotDTO[] {
    return this.calendarGrid[this.getDayIndex(date)].flatMap(
      ({ slots }) => slots
    );
  }

  getDayColor(date: Date): string {
    return this.workCalendar.getDay(date).workDay ? 'bg-gray-4' : 'bg-red-4';
  }

  getDayDescription(date: Date): string {
    return this.workCalendar.getDay(date).description || '';
  }

  private static splitTickets(tickets: TicketDTO[]): Record<string, TicketDTO[]> {
    return tickets.reduce((byStatuses, ticket) => {
      const index = ticket.status?.statusCode || 'other';
      if (!byStatuses[index]) byStatuses[index] = [];
      byStatuses[index].push(ticket);
      return byStatuses;
    }, {} as Record<string, TicketDTO[]>);
  }

  ticketStatus(status: keyof typeof TICKET_STATUS): string {
    return TICKET_STATUS[status];
  }

  ticketIs(ticket: TicketDTO, status: keyof typeof TICKET_STATUS): boolean {
    return ticket.status?.statusCode === TICKET_STATUS[status];
  }

  getStatusColor(statusCode?: string): string {
    if (!statusCode) return '';
    return TICKET_STATUS_COLORS[statusCode] || '';
  }

  showSlots(slot: GridSlot): void {
    console.log(slot.slots.map(({ slotId }) => slotId));
  }

  selectSlot(slot: GridSlot) {
    console.log('clicked slot', slot)
    if (isEmpty(slot.tickets) || slot.ticketsByStatus[301]) {

      if (this.isBooked) {
        this.$emit('cancelReservation', () => this.selectSlot(slot));
      } else {
        const reservation = this.selected?.slots?.[0]?.slotId === slot.slots?.[0]?.slotId
          ? null
          : slot;
        this.$emit('selectTicket', reservation);
      }
    }
  }

  public setWeek(val: any, reason: string, { day, month, year }: Record<string, string>): boolean {
    this.calcDateRange(new Date([year,month, day].join('/')));
    return false;
  }

  public prevWeek(): void {
    this.calcDateRange(shiftDateTo(this.dates[0], -7));
  }

  public nextWeek(): void {
    this.calcDateRange(shiftDateTo(this.dates[0], 7));
  }

  displayDate(date: Date): string {
    return convertDateFormat(
        date,
        '',
        DateFormat.date,
    );
  }

  async fetchApplicationId(orderId: string | undefined): Promise<string | undefined> {
    if (!orderId) return;
    const { data } = await documentApi.searchDocuments('Applications', `(common.orderId==${orderId})`);
    const document = data.find((document) => {
      return String(get(document, 'common.orderId')) === orderId;
    });
    return document?.id;
  }

  async fetchApplications(orderIds: string | string[] = ''): Promise<Record<string, Document>> {
    if (!Array.isArray(orderIds)) {
      orderIds = orderIds.split(',');
    }
    orderIds = orderIds.map((id) => `'${id}'`);
    const { data } = await documentApi.searchDocuments('Applications', `(common.orderId =in= (${orderIds.join(',')}))`);

    const results = data.reduce((results, document) => {
      results[String(get(document, 'common.orderId'))] = document;
      return results;
    }, {} as Record<string, Document>);
    return results;
  }

  async fetch(
    organizationId: string,
    serviceId?: string,
    areaId?: string
  ): Promise<void> {
    if (!serviceId) {
      notifyError('Невозможно получить слоты если не задана услуга');
      return;
    }
    try {
      this.slots = await preorderService.getSlots({
        organizationId,
        serviceId,
        areaId,
      });
    } catch (error) {
      this.slots = [];
    }
  }

  columns: TableColumn<TicketDTO>[] = [
    {
      name: 'fullName',
      label: this.$t('SlotsCalendar.labels.fullName'),
      align: 'left',
      style: 'white-space: normal;',
      field: identity,
    },
    {
      name: 'caseNumber',
      label: this.$t('SlotsCalendar.labels.caseNumber'),
      align: 'left',
      style: 'white-space: normal;',
      field: identity,
      formatField: 'caseNumber',
    },
    {
      name: 'actions',
      label: this.$t('SlotsCalendar.labels.actions'),
      align: 'right',
      style: 'white-space: normal;',
      field: identity,
    },
  ];
};
</script>

<style lang="scss" scoped>
.slots-calendar {

  &__container {
    position: relative;
    overflow: auto;
    padding: 0 8px 0;
    margin: 0 -8px 0;
  }

  &__header {
    position: sticky;
    top: 0;
    z-index: 5;
    user-select: none;
    background: #ffffff;
  }

  &__row {
    display: flex;
    flex-flow: row nowrap;
    justify-content: stretch;
    gap: 1em;
  }

  &__day {
    display: flex;
    flex-flow: column nowrap;
    flex: 1 1 14.258%;
    text-align: center;
    align-items: stretch;
    align-content: flex-start;
    justify-content: flex-start;
    background: #e0e0e0;

    &-header {
      display: inline-flex;
      flex-direction: column;
      align-items: stretch;
      position: relative;
      vertical-align: middle;
      font-size: 14px;
      line-height: 1.715em;
      text-decoration: none;
      color: inherit;
      font-weight: 500;
      text-transform: uppercase;
      text-align: center;
      width: auto;
      height: auto;
      flex: 0 1 auto;
      padding: 4px;
      border-radius: 4px 4px 0 0;
    }
  }

  &__time-text {
    position: relative;
    display: inline-block;
    padding-right: 1em;
    padding-left: 1em;
  }

  &__time-slot {
    position: relative;
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #EEEEEEFF;
    color: #c6c6c6;
    user-select: none;
    cursor: not-allowed;
    
    &--past {
      background: #efefef;
    }

    &--has-slots {
      font-weight: normal;
      color: black;
      cursor: pointer;

      &:hover {
        color: #5c7e9a;
      }
    }

    &--selected {
    cursor: pointer;
      background: #3f68b38a;
    }
  }

  &__badeges-parking {
    position: absolute;
    top: 0;
    right: 0;
  }

  &__floating-badges {
    display: flex;
    flex-wrap: nowrap;
    position: absolute;
    top: -4px;
    left: 0;
    cursor: inherit;
    gap: 2px;
  }

  &__time-string {
    display: block;
  }
}
</style>
