import { observer } from 'mobx-react-lite';
import { Instance, onSnapshot, types } from 'mobx-state-tree';
import React from 'react';
import * as Luxon from 'luxon';
import * as DashboardStoreNs from '../dashboard-store';
import { DateTime } from 'luxon';
import { IssueHref } from '../../misc-components/issue-href';

// Описание основных и вспомогательных моделей данных для календаря:

export const CalendarEvent = types.model({
  from: types.string,
  fromTimestamp: types.number,
  to: types.string,
  toTimestamp: types.number,
  fullDay: types.boolean,
  description: types.string,
});

export type ICalendarEvent = Instance<typeof CalendarEvent>;

export const FormattedDateTime = types.model({
  fromDate: types.string,
  fromTime: types.string,
  toDate: types.string,
  toTime: types.string,
  interval: types.string,
});

export type IFormattedDateTime = Instance<typeof FormattedDateTime>;

export const IssueAndEvent = types.model({
  issue: types.frozen<any>(),
  event: CalendarEvent,
});

export type IIssueAndEvent = Instance<typeof IssueAndEvent>;

type InDay = {
  order: number;
  relativeDate: string | null;
  formattedDate: string;
  events: string[];
};

export const DATE_FORMAT = 'dd.MM.yyyy';
export const TIME_FORMAT = 'HH:mm';

function getEventKey(e: IIssueAndEvent): string {
  const description: string = e.event.description.replaceAll(/\s+/g, '_');
  const fromKey = String(e.event.fromTimestamp);
  const toKey = String(e.event.toTimestamp);
  return `${e.issue.id}-${fromKey}-${toKey}-${description}`;
}

function getFormattedDateTime(
  issueAndEvent: IIssueAndEvent,
): IFormattedDateTime {
  const from = DateTime.fromMillis(issueAndEvent.event.fromTimestamp);
  const to = DateTime.fromMillis(issueAndEvent.event.toTimestamp);
  const fromDate: string = from.isValid ? from.toFormat(DATE_FORMAT) : '-';
  const fromTime: string = from.isValid ? from.toFormat(TIME_FORMAT) : '-';
  const toDate: string = to.isValid ? to.toFormat(DATE_FORMAT) : '-';
  const toTime: string = to.isValid ? to.toFormat(TIME_FORMAT) : '-';
  let interval: string;
  if (issueAndEvent.event.fullDay) {
    interval = 'весь день';
  } else if (toTime != '-') {
    interval = `${fromTime} - ${toTime}`;
  } else {
    interval = `${fromTime}`;
  }
  return FormattedDateTime.create({
    fromDate: fromDate,
    fromTime: fromTime,
    toDate: toDate,
    toTime: toTime,
    interval: interval,
  });
}

function getRelativeDate(issueAndEvent: IIssueAndEvent): string | null {
  const from = Luxon.DateTime.fromMillis(issueAndEvent.event.fromTimestamp);
  return from.toRelativeCalendar();
}

function getStartOfDayTimestamp(issueAndEvent: IIssueAndEvent): number {
  const from = Luxon.DateTime.fromMillis(issueAndEvent.event.fromTimestamp);
  return from.startOf('day').toMillis();
}

/** Стор данных для виджета "Календарь следующих событий" */
export const EventsStore = types
  .model({
    events: types.array(IssueAndEvent),
  })
  .views((self) => {
    return {
      /** Геттер мапы событий в формате eventKey -> issue+event */
      eventsMap: (): Record<string, IIssueAndEvent> => {
        return self.events.reduce((acc, issueAndEvent) => {
          const key = getEventKey(issueAndEvent);
          acc[key] = issueAndEvent;
          return acc;
        }, {} as Record<string, IIssueAndEvent>);
      },

      /**
       * Геттер сгруппированных по дням данных.
       *
       * Ключ группы определяется по timestamp-у указывающему на начало дня
       *
       * Дополнительные поля relativeDate и formattedDate для отображения в виджете подробной
       * информации об этой группе событий
       */
      orderedByDates: (): InDay[] => {
        const res: Record<number, InDay> = self.events.reduce(
          (acc, issueAndEvent) => {
            const order = getStartOfDayTimestamp(issueAndEvent);
            const formattedDate = DateTime.fromMillis(
              issueAndEvent.event.fromTimestamp,
            ).toFormat(DATE_FORMAT);
            if (!acc[order]) {
              acc[order] = {
                order: order,
                relativeDate: getRelativeDate(issueAndEvent),
                formattedDate: formattedDate,
                events: [],
              };
            }
            const key = getEventKey(issueAndEvent);
            acc[order].events.push(key);
            return acc;
          },
          {} as Record<number, InDay>,
        );
        return Object.values(res).sort((a, b) => a.order - b.order);
      },

      /**
       * Мапа событий с отформатированными значениями даты и времени
       *
       * * Ключ - это ключ события
       * * Значение - вспомогательная информация для человеко-читаемого вывода даты и времени
       */
      formattedDateTimes: (): Record<string, IFormattedDateTime> => {
        const res: Record<string, IFormattedDateTime> = self.events.reduce(
          (acc, event) => {
            const key = getEventKey(event);
            acc[key] = getFormattedDateTime(event);
            return acc;
          },
          {} as Record<string, IFormattedDateTime>,
        );
        return res;
      },
    };
  })
  .actions((self) => {
    return {
      /** Сеттер основных данных */
      setEvents: (events: any): void => {
        self.events = events;
      },
    };
  });

export type IEventsStore = Instance<typeof EventsStore>;

/**
 * Компонент для отображения данных из стора EventsStore
 *
 * @see {EventsStore}
 */
export const CalendarList = observer(
  (props: { store: IEventsStore }): JSX.Element => {
    const list = props.store.orderedByDates().map((events) => {
      const keyOfGroup = `${events.order}-${events.relativeDate}`;
      const item = (
        <div key={keyOfGroup}>
          <p title={events.formattedDate}>{events.relativeDate}:</p>
          <ul>
            {events.events.map((keyOfEvent) => {
              const events = props.store.eventsMap();
              const formatted = props.store.formattedDateTimes();
              if (!events[keyOfEvent] && !formatted[keyOfEvent]) return <></>;
              const issue = events[keyOfEvent].issue;
              return (
                <li key={keyOfEvent}>
                  {formatted[keyOfEvent].interval}:{' '}
                  <IssueHref
                    id={issue.id}
                    subject={events[keyOfEvent].event.description}
                    tracker={issue.tracker.name}
                    url={issue.url.url}
                  />
                </li>
              );
            })}
          </ul>
        </div>
      );
      return item;
    });
    return <>{list}</>;
  },
);

export type Props = {
  store: DashboardStoreNs.IWidget;
};

/**
 * Основной компонент календаря
 *
 * Он нужен для преобразования стора абстрактного виджета в стор специфичный для
 * календаря
 */
export const CalendarNextEvents = observer((props: Props): JSX.Element => {
  const calendarListStore = EventsStore.create();
  onSnapshot(props.store, (storeState) => {
    if (storeState.data) {
      calendarListStore.setEvents(storeState.data);
    }
  });
  return (
    <>
      <CalendarList store={calendarListStore} />
    </>
  );
});
