import {
  endOfWeek,
  startOfToday,
  startOfWeek,
  isAfter,
  eachDayOfInterval,
  addWeeks,
  parseISO,
} from "date-fns";
import { toLocalTime, formatDate, compareDates } from "./date";
import { graphql, unmaskFragment } from "@/gql";
import {
  type EventCategoryFragment,
  type EventFragment,
  type EventListItemFragment,
  type EventOrganizerFragment,
  type EventOverviewItemFragment,
} from "@/gql/graphql";
import { PIG_NAMES, randomElement } from "@/lib/common";

export type EventOccurrence = EventFragment["occurrences"][number];
export type EventListItemOccurrence = EventListItemFragment["occurrences"][number];
export type EventCategory = EventCategoryFragment;
export type EventOrganizer = EventOrganizerFragment;
export type { EventFragment, EventListItemFragment, EventOverviewItemFragment };

type EventListable = {
  occurrences: ReadonlyArray<{ id: string | null; start: string; end: string | null }>;
};

export type SingularEvent<E extends EventListable = EventFragment> = E & {
  occurrence: E["occurrences"][number];
};

export const EventCategoryFragmentDefinition = graphql(`
  fragment EventCategory on EventCategory {
    __typename
    name
    slug
    pig
    showInFilters
  }
`);

export const EventOrganizerFragmentDefinition = graphql(`
  fragment EventOrganizer on EventOrganizer {
    __typename
    id
    name
    slug
    externalUrl
    association {
      ... on AssociationPage {
        url
      }
    }
  }
`);

const EventListItemFragmentDefinition = graphql(`
  fragment EventListItem on EventPage {
    __typename
    id
    slug
    title
    subtitle
    featuredImage {
      ...Image
    }
    occurrences(limit: 5000) {
      ... on EventOccurrence {
        __typename
        id
        start
        end
      }
    }
  }
`);

const EventOverviewItemFragmentDefinition = graphql(`
  fragment EventOverviewItem on EventPage {
    __typename
    id
    slug
    title
    subtitle
    featuredImage {
      ...Image
    }
    categories {
      ... on EventCategory {
        ...EventCategory
      }
    }
    occurrences(limit: 5000) {
      ... on EventOccurrence {
        __typename
        id
        start
        end
        venue {
          __typename
          id
          slug
          title
          preposition
          url
        }
        venueCustom
      }
    }
    organizers {
      ... on EventOrganizer {
        ...EventOrganizer
      }
    }
  }
`);

const EventFragmentDefinition = graphql(`
  fragment Event on EventPage {
    __typename
    id
    slug
    seoTitle
    searchDescription
    title
    subtitle
    lead
    body {
      ...OneLevelOfBlocks
    }
    featuredImage {
      ...Image
    }
    pig
    facebookUrl
    ticketUrl
    free
    priceRegular
    priceMember
    priceStudent
    categories {
      ... on EventCategory {
        ...EventCategory
      }
    }
    occurrences(limit: 5000) {
      ... on EventOccurrence {
        __typename
        id
        start
        end
        venue {
          __typename
          id
          slug
          title
          preposition
          url
        }
        venueCustom
      }
    }
    organizers {
      ... on EventOrganizer {
        ...EventOrganizer
      }
    }
  }
`);

const EventIndexFragmentDefinition = graphql(`
  fragment EventIndex on EventIndex {
    __typename
    id
    slug
    seoTitle
    searchDescription
    title
  }
`);

export const eventIndexMetadataQuery = graphql(`
  query eventIndexMetadata {
    index: eventIndex {
      ... on EventIndex {
        ...EventIndex
      }
    }
  }
`);

export const eventsOverviewQuery = graphql(`
  query futureEvents {
    index: eventIndex {
      ... on EventIndex {
        ...EventIndex
      }
    }
    events: eventIndex {
      ... on EventIndex {
        futureEvents {
          ... on EventPage {
            ...EventOverviewItem
          }
        }
      }
    }
    eventCategories: eventCategories(limit: 5000) {
      ... on EventCategory {
        ...EventCategory
      }
    }
    eventOrganizers: eventOrganizers(limit: 5000) {
      ... on EventOrganizer {
        ...EventOrganizer
      }
    }
    venues: pages(contentType: "venues.VenuePage") {
      ... on VenuePage {
        id
        title
        slug
        preposition
      }
    }
  }
`);

export function getSingularEvents<E extends EventListable>(
  events: E[]
): SingularEvent<E>[] {
  return events.flatMap((event) =>
    event.occurrences.map((occurrence) => {
      const eventOccurrence = structuredClone(event) as SingularEvent<E>;
      eventOccurrence.occurrence = occurrence;
      return eventOccurrence;
    })
  );
}

export function sortSingularEvents<E extends EventListable>(
  events: SingularEvent<E>[]
) {
  return events.sort((a, b) =>
    compareDates(a.occurrence.start, b.occurrence.start)
  );
}
interface EventCalendar<E extends EventListable = EventFragment> {
  [yearMonth: string]: {
    [week: string]: {
      [day: string]: SingularEvent<E>[];
    };
  };
}

export function organizeEventsInCalendar<E extends EventListable>(
  events: SingularEvent<E>[]
): EventCalendar<E> {
  const sortedEvents = sortSingularEvents(events);

  const calendar: EventCalendar<E> = {};

  const minDate = new Date(sortedEvents[0]?.occurrence.start);
  const maxDate = new Date(
    sortedEvents[sortedEvents.length - 1]?.occurrence.start
  );

  let currentDate = startOfWeek(minDate, { weekStartsOn: 1 });
  while (currentDate <= maxDate) {
    const week = formatDate(currentDate, "w");
    const daysOfWeek = eachDayOfInterval({
      start: currentDate,
      end: endOfWeek(currentDate, { weekStartsOn: 1 }),
    });

    daysOfWeek.forEach((day) => {
      const yearMonth = formatDate(day, "yyyy-MM");
      if (!calendar[yearMonth]) {
        calendar[yearMonth] = {};
      }
      if (!calendar[yearMonth][week]) {
        calendar[yearMonth][week] = {};
      }
      const formattedDay = formatDate(day, "yyyy-MM-dd");
      if (!calendar[yearMonth][week][formattedDay]) {
        calendar[yearMonth][week][formattedDay] = [];
      }
    });

    currentDate = addWeeks(currentDate, 1);
  }

  sortedEvents.forEach((event) => {
    const start = toLocalTime(event.occurrence.start);
    const yearMonth = formatDate(start, "yyyy-MM");
    const week = formatDate(start, "w");
    const day = formatDate(start, "yyyy-MM-dd");
    if (!calendar[yearMonth]) {
      calendar[yearMonth] = {};
    }
    if (!calendar[yearMonth][week]) {
      calendar[yearMonth][week] = {};
    }
    if (!calendar[yearMonth][week][day]) {
      calendar[yearMonth][week][day] = [];
    }
    calendar[yearMonth][week][day].push(event);
  });

  return calendar;
}

interface EventsByDate<E extends EventListable = EventFragment> {
  [day: string]: SingularEvent<E>[];
}

export function organizeEventsByDate<E extends EventListable>(
  events: SingularEvent<E>[]
): EventsByDate<E> {
  const sortedEvents = sortSingularEvents(events);
  const eventsByDate: EventsByDate<E> = {};

  sortedEvents.forEach((event) => {
    const start = toLocalTime(event.occurrence.start);
    const day = formatDate(start, "yyyy-MM-dd");
    if (!eventsByDate[day]) {
      eventsByDate[day] = [];
    }
    eventsByDate[day].push(event);
  });

  return eventsByDate;
}

export function getFutureOccurrences<E extends EventListable>(
  event: E
): E["occurrences"][number][] {
  const today = startOfToday();
  const occurrences = event?.occurrences ?? [];
  const futureOccurrences = occurrences.filter((occurrence) =>
    isAfter(toLocalTime(occurrence.start), today)
  );
  futureOccurrences.sort(
    (a, b) => parseISO(a.start).getTime() - parseISO(b.start).getTime()
  );
  return futureOccurrences as E["occurrences"][number][];
}

export function getEventPig(event: EventFragment): string | null {
  if (event.pig === "" || typeof event.pig !== "string") {
    return null;
  }
  if (PIG_NAMES.includes(event.pig)) {
    return event.pig;
  }
  if (event.pig === "automatic") {
    const categories = unmaskFragment(
      EventCategoryFragmentDefinition,
      event.categories
    );
    const categoryPigs = categories
      ?.map((category) => category.pig)
      .filter((pig) => PIG_NAMES.includes(pig));
    const chosenPig = randomElement(categoryPigs ?? []);
    return typeof chosenPig === "string" ? chosenPig : null;
  }
  return null;
}
