import { parse } from "date-fns";
import keyBy from "lodash/keyBy";
import pick from "lodash/pick";
import property from "lodash/property";
import { createAsyncAction, errorResult, successResult } from "pullstate";

import { fetch } from "api";
import { markdownConverter } from "markdown";
import { ProgramStore } from "stores";

import { loadPosts } from "./posts";

export const loadProgram = createAsyncAction(
  async () => {
    try {
      const resp = await fetch("/program");
      const mappings = await resp.json();
      return successResult(mappings);
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: ({ result }) => {
      if (!result.error) {
        const entries = result.payload
          .map(
            /**
             *
             * @param {any} entry
             * @returns {CF2021.ProgramScheduleEntry}
             */
            (entry) => {
              return {
                ...pick(entry, [
                  "id",
                  "number",
                  "title",
                  "description",
                  "proposer",
                  "speakers",
                ]),
                fullTitle:
                  entry.number !== ""
                    ? `${entry.number}. ${entry.title}`
                    : entry.title,
                htmlContent: markdownConverter.makeHtml(entry.description),
                discussionOpened: entry.discussion_opened,
                expectedStartAt: parse(
                  entry.expected_start_at,
                  "yyyy-MM-dd HH:mm:ss",
                  new Date()
                ),
                expectedFinishAt: entry.expected_finish_at
                  ? parse(
                      entry.expected_finish_at,
                      "yyyy-MM-dd HH:mm:ss",
                      new Date()
                    )
                  : undefined,
              };
            }
          )
          .sort((a, b) => a.expectedStartAt - b.expectedStartAt);

        const currentEntry = result.payload.find((entry) => entry.is_live);

        ProgramStore.update((state) => {
          state.items = keyBy(entries, property("id"));
          state.scheduleIds = entries.map((entry) => entry.id);

          if (currentEntry) {
            state.currentId = currentEntry.id;
          }
        });
      }
    },
  }
);

/**
 * Rename program point.
 */
export const renameProgramPoint = createAsyncAction(
  async ({ programEntry, newTitle }) => {
    try {
      const body = JSON.stringify({
        title: newTitle,
      });
      await fetch(`/program/${programEntry.id}`, {
        method: "PUT",
        body,
        expectedStatus: 204,
      });
      return successResult({ programEntry, newTitle });
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: ({ result }) => {
      if (!result.error) {
        ProgramStore.update((state) => {
          if (state.items[result.payload.programEntry.id]) {
            state.items[result.payload.programEntry.id].title =
              result.payload.newTitle;
          }
        });
      }
    },
  }
);

/**
 * End program point.
 */
export const endProgramPoint = createAsyncAction(
  /**
   *
   * @param {CF2021.ProgramScheduleEntry} programEntry
   */
  async (programEntry) => {
    try {
      const body = JSON.stringify({
        is_live: false,
      });
      await fetch(`/program/${programEntry.id}`, {
        method: "PUT",
        body,
        expectedStatus: 204,
      });
      return successResult(programEntry);
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: ({ result }) => {
      if (!result.error) {
        ProgramStore.update((state) => {
          state.currentId = null;
        });
      }
    },
  }
);

/**
 * Activate program point.
 */
export const activateProgramPoint = createAsyncAction(
  /**
   *
   * @param {CF2021.ProgramScheduleEntry} programEntry
   */
  async (programEntry) => {
    try {
      const body = JSON.stringify({
        is_live: true,
      });
      await fetch(`/program/${programEntry.id}`, {
        method: "PUT",
        body,
        expectedStatus: 204,
      });
      return successResult(programEntry);
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: async ({ result }) => {
      if (!result.error) {
        ProgramStore.update((state) => {
          state.currentId = result.payload.id;
        });

        // Re-load posts - these are bound directly to the program schedule entry.
        loadPosts.run({}, { respectCache: false });
      }
    },
  }
);

/**
 * Open discussion.
 */
export const openDiscussion = createAsyncAction(
  /**
   *
   * @param {CF2021.ProgramScheduleEntry} programEntry
   */
  async (programEntry) => {
    try {
      const body = JSON.stringify({
        discussion_opened: true,
      });
      await fetch(`/program/${programEntry.id}`, {
        method: "PUT",
        body,
        expectedStatus: 204,
      });
      return successResult(programEntry);
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: ({ result }) => {
      if (!result.error) {
        ProgramStore.update((state) => {
          if (state.items[result.payload.id]) {
            state.items[result.payload.id].discussionOpened = true;
          }
        });
      }
    },
  }
);

/**
 * Close discussion.
 */
export const closeDiscussion = createAsyncAction(
  async (programEntry) => {
    try {
      const body = JSON.stringify({
        discussion_opened: false,
      });
      await fetch(`/program/${programEntry.id}`, {
        method: "PUT",
        body,
        expectedStatus: 204,
      });
      return successResult(programEntry);
    } catch (err) {
      return errorResult([], err.toString());
    }
  },
  {
    postActionHook: ({ result }) => {
      if (!result.error) {
        ProgramStore.update((state) => {
          if (state.items[result.payload.id]) {
            state.items[result.payload.id].discussionOpened = false;
          }
        });
      }
    },
  }
);