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

export type {
  EventFragment,
  EventCategory,
  EventOccurrence,
  EventOrganizer,
} from "@/gql/graphql";

export type SingularEvent = EventFragment & {
  occurrence: EventOccurrence;
};

const EventFragmentDefinition = graphql(`
  fragment Event on EventPage {
    __typename
    id
    slug
    title
    subtitle
    body {
      id
      blockType
      field
      ... on RichTextBlock {
        rawValue
        value
      }
    }
    featuredImage {
      ...Image
    }
    pig
    facebookUrl
    ticketUrl
    free
    priceRegular
    priceMember
    priceStudent
    categories {
      ... on EventCategory {
        name
        slug
        pig
      }
    }
    occurrences {
      ... on EventOccurrence {
        __typename
        id
        start
        end
        venue {
          __typename
          id
          slug
          title
          preposition
          url
        }
      }
    }
    organizers {
      ... on EventOrganizer {
        id
        name
        slug
        externalUrl
        association {
          ... on AssociationPage {
            url
          }
        }
      }
    }
  }
`);

export const futureEventsQuery = graphql(`
  query futureEvents {
    events: eventIndex {
      ... on EventIndex {
        futureEvents {
          ... on EventPage {
            ...Event
          }
        }
      }
    }
    eventCategories: eventCategories {
      ... on EventCategory {
        name
        slug
        showInFilters
      }
    }
    eventOrganizers: eventOrganizers {
      ... on EventOrganizer {
        id
        name
        slug
        externalUrl
        association {
          ... on AssociationPage {
            url
          }
        }
      }
    }
    venues: pages(contentType: "venues.VenuePage") {
      ... on VenuePage {
        id
        title
        slug
        preposition
      }
    }
  }
`);

export function getSingularEvents(events: EventFragment[]): SingularEvent[] {
  return events
    .map((event) => {
      return event.occurrences.map((occurrence) => {
        const eventOccurrence: any = structuredClone(event);
        eventOccurrence.occurrence = occurrence;
        return eventOccurrence;
      });
    })
    .flat();
}

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

export function organizeEventsInCalendar(
  events: SingularEvent[]
): EventCalendar {
  const sortedEvents = sortSingularEvents(events);

  const calendar: EventCalendar = {};

  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 {
  [day: string]: SingularEvent[];
}

export function organizeEventsByDate(events: SingularEvent[]): EventsByDate {
  const sortedEvents = sortSingularEvents(events);
  const eventsByDate: EventsByDate = {};

  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(event: EventFragment): EventOccurrence[] {
  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 EventOccurrence[];
}

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 categoryPigs = event.categories
      ?.map((category) => category.pig)
      .filter((pig) => PIG_NAMES.includes(pig));
    const chosenPig = randomElement(categoryPigs ?? []);
    return typeof chosenPig === "string" ? chosenPig : null;
  }
  return null;
}
