"use client";

import { useEffect } from "react";
import {
  useQueryState,
  parseAsStringLiteral,
  parseAsString,
  parseAsArrayOf,
} from "nuqs";
import { EventItem } from "./EventItem";
import { EventFilter, EventFilterExplained } from "./EventFilter";
import { unmaskFragment } from "@/gql";
import {
  EventCategoryFragmentDefinition,
  EventOrganizerFragmentDefinition,
  EventOverviewItemFragment,
  EventCategory,
  SingularEvent,
  getSingularEvents,
  organizeEventsInCalendar,
  EventOrganizer,
} from "@/lib/event";
import { isTodayOrFuture } from "@/lib/date";
import styles from "./eventContainer.module.scss";
import { formatDate, formatYearMonth } from "@/lib/date";
import {
  startOfWeek,
  endOfWeek,
  differenceInCalendarDays,
  parse,
} from "date-fns";
import { unique } from "@/lib/common";
import { Icon } from "../general/Icon";
import { useState } from "react";
import { VenueFragment } from "@/gql/graphql";

/*
  TODO: canonical / alternate URLs https://github.com/47ng/nuqs?tab=readme-ov-file#seo
*/

export const EventContainer = ({
  events,
  eventCategories,
  eventOrganizers,
  venues,
}: {
  events: EventOverviewItemFragment[];
  eventCategories: EventCategory[];
  eventOrganizers: EventOrganizer[];
  venues: VenueFragment[];
}) => {
  const [mode, setMode] = useQueryState(
    "mode",
    parseAsStringLiteral(["list", "calendar"]).withDefault("list"),
  );
  const [categories, setCategories] = useQueryState(
    "category",
    parseAsArrayOf(parseAsString, ","),
  );
  const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
  const [venue, setVenue] = useQueryState("venue", parseAsString);

  const resetFilters = () => {
    setCategories(null);
    setOrganizer(null);
    setVenue(null);
  };

  const onResetFilters = (e: React.MouseEvent<HTMLButtonElement>) => {
    resetFilters();
  };

  /* Allow filtering on all categories that are configured to be shown */
  const filterableCategories = eventCategories.filter((x) => x.showInFilters);

  /*
    Allow filtering on all organizers that have upcoming events
    Filtering on an organizer with no upcoming events will work,
    and in that case it's included in the dropdown
  */
  const allOrganizers = unmaskFragment(
    EventOrganizerFragmentDefinition,
    events.flatMap((x) => x.organizers),
  );
  const uniqueOrganizers: string[] = unique(
    allOrganizers
      .filter((x) => x.__typename === "EventOrganizer")
      .map((x) => x.slug)
      .filter((x) => typeof x === "string" && x !== ""),
  );
  const filterableOrganizers = uniqueOrganizers
    .map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
    .filter((x) => x !== undefined) as EventOrganizer[];

  // If an organizer is selected that's not in the upcoming list, add it
  if (
    organizer &&
    !filterableOrganizers.some((org) => org.slug === organizer)
  ) {
    const selectedOrganizer = eventOrganizers.find(
      (org) => org.slug === organizer,
    );
    if (selectedOrganizer) {
      filterableOrganizers.push(selectedOrganizer);
    }
  }

  /* Reset organizer if slug is invalid */
  useEffect(() => {
    if (!eventOrganizers.find((haystack) => haystack.slug === organizer)) {
      setOrganizer(null);
    }
  }, [eventOrganizers, organizer]);

  /*
    Allow filtering on all venues that have upcoming events
    Filtering on a venue with no upcoming events will work,
    and in that case it's included in the dropdown
   */
  const venueSlugsWithUpcomingEvents = unique(
    events
      .map((x) => x.occurrences)
      .flat()
      .filter((x) => x.venue?.__typename === "VenuePage")
      .map((x) => x.venue?.slug)
      .filter((x) => typeof x === "string"),
  );
  const filterableVenues = venues
    .filter(
      (x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue,
    )
    .map((x) => venues.find((haystack) => haystack.slug === x.slug))
    .filter((x) => x !== undefined) as VenueFragment[];

  /* Reset venue if slug is invalid */
  useEffect(() => {
    if (!venues.find((haystack) => haystack.slug === venue)) {
      setVenue(null);
    }
  }, [venues, venue]);

  const filteredEvents = events.filter((event) => {
    if (organizer) {
      const organizers = unmaskFragment(
        EventOrganizerFragmentDefinition,
        event.organizers,
      );
      if (!organizers.some((o) => o.slug === organizer)) {
        return false;
      }
    }
    if (categories) {
      const eventCategories = unmaskFragment(
        EventCategoryFragmentDefinition,
        event.categories,
      );
      if (!eventCategories.some((c) => categories.includes(c.slug))) {
        return false;
      }
    }
    if (venue) {
      if (!event.occurrences.some((occ) => occ.venue?.slug === venue)) {
        return false;
      }
    }
    return true;
  });

  const [showFilter, setShowFilter] = useState(false);
  function toggleFilter() {
    setShowFilter(!showFilter);
  }

  return (
    <div className={styles.events}>
      <div className={styles.eventWrapper}>
        <div className={styles.displayOptions}>
          <div className={styles.displayButtons}>
            <button
              onClick={() => setMode(null)}
              className="button toggler hasIcon"
              data-active={mode === "list"}
            >
              <span>Vis liste</span>
              <Icon type="list" />
            </button>
            <button
              onClick={() => setMode("calendar")}
              className="button toggler hasIcon"
              data-active={mode === "calendar"}
            >
              <span>Vis kalender</span>
              <Icon type="calendar" />
            </button>
          </div>
          <div className={styles.filterButtons}>
            {(categories || organizer || venue) && (
              <button onClick={onResetFilters} className="button tertiary">
                <span>Vis alle</span>
                <Icon type="noFilter" />
              </button>
            )}
            <button onClick={toggleFilter} className="button tertiary">
              <span>Filter</span>
              <Icon type="filter" />
            </button>
          </div>
        </div>
        <EventFilter
          eventCategories={filterableCategories}
          setCategories={setCategories}
          activeCategories={categories}
          eventOrganizers={filterableOrganizers}
          setOrganizer={setOrganizer}
          activeOrganizer={organizer}
          venues={filterableVenues}
          setVenue={setVenue}
          activeVenue={venue}
          isVisible={showFilter}
        />
        <EventFilterExplained
          eventCategories={filterableCategories}
          activeCategories={categories}
          eventOrganizers={filterableOrganizers}
          activeOrganizer={organizer}
          venues={filterableVenues}
          activeVenue={venue}
        />
        {mode === "list" && <EventList events={filteredEvents} />}
        {mode === "calendar" && <EventCalendar events={filteredEvents} />}
      </div>
    </div>
  );
};

const EventList = ({ events }: { events: EventOverviewItemFragment[] }) => {
  if (events.length === 0) {
    return <div className={styles.noEvents}>Ingen kommende arrangementer.</div>;
  }
  return (
    <ul className={styles.eventList}>
      {events.map((event) => (
        <EventItem key={event.id} event={event} mode="list" />
      ))}
    </ul>
  );
};

const EmptyCalendarCell = () => (
  <div className={`${styles.calendarDay} ${styles.empty}`} />
);

const CalendarDay = ({
  day,
  events,
}: {
  day: string;
  events: SingularEvent<EventOverviewItemFragment>[];
}) => (
  <div
    className={`${styles.calendarDay} ${events.length === 0 && styles.empty}`}
  >
    <h3>{formatDate(parse(day, "yyyy-MM-dd", new Date()), "eeee dd.MM.")}</h3>
    <ul>
      {events.map((event) => (
        <EventItem
          key={`${day}--${event.id}--${event.occurrence.id}`}
          event={event}
          mode="calendar"
          size="small"
        />
      ))}
    </ul>
  </div>
);

const CalendarWeek = ({
  days,
}: {
  days: Record<string, SingularEvent<EventOverviewItemFragment>[]>;
}) => {
  const daysInWeek = Object.keys(days);
  const firstDay = daysInWeek[0];
  const lastDay = daysInWeek[daysInWeek.length - 1];

  const weekStart = startOfWeek(firstDay, { weekStartsOn: 1 });
  const weekEnd = endOfWeek(lastDay, { weekStartsOn: 1 });

  const emptyCellsBefore = differenceInCalendarDays(firstDay, weekStart);
  const emptyCellsAfter = differenceInCalendarDays(weekEnd, lastDay);

  return (
    <div className={styles.calendarWeek}>
      {[...Array(emptyCellsBefore)].map((_, idx) => (
        <EmptyCalendarCell key={`before-${idx}`} />
      ))}
      {Object.keys(days).map((day) => (
        <CalendarDay key={day} day={day} events={days[day]} />
      ))}
      {[...Array(emptyCellsAfter)].map((_, idx) => (
        <EmptyCalendarCell key={`after-${idx}`} />
      ))}
    </div>
  );
};

function maybeYear(yearMonthString: string) {
  const yearMonth = new Date(yearMonthString);
  const now = new Date();
  const isCurrentYear = yearMonth.getFullYear() == now.getFullYear();
  if (isCurrentYear) {
    return <span className="sr-only"> {yearMonth.getFullYear()}</span>;
  }
  return ` ${yearMonth.getFullYear()}`;
}

const EventCalendar = ({ events }: { events: EventOverviewItemFragment[] }) => {
  const futureSingularEvents = getSingularEvents(events).filter(
    (x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start),
  );
  const eventsByDate = organizeEventsInCalendar(futureSingularEvents);
  const yearMonths = Object.keys(eventsByDate);
  const [visibleYearMonths, setVisibleYearMonths] = useState(
    yearMonths.slice(0, 2),
  );

  const toggleYearMonth = (yearMonth: string) => {
    if (visibleYearMonths.includes(yearMonth)) {
      setVisibleYearMonths(visibleYearMonths.filter((x) => x != yearMonth));
    } else {
      setVisibleYearMonths([...visibleYearMonths, yearMonth]);
    }
  };

  return (
    <>
      <div className={styles.eventCalendar}>
        {Object.keys(eventsByDate).map((yearMonth) => {
          const eventCount = Object.values(eventsByDate[yearMonth]).reduce(
            (yearMonthSum, week) =>
              yearMonthSum +
              Object.values(week).reduce(
                (weekSum, day) => weekSum + day.length,
                0,
              ),
            0,
          );

          return (
            <div
              key={yearMonth}
              className={styles.calendarYearMonth}
              data-collapsed={!visibleYearMonths.includes(yearMonth)}
            >
              <h2 onClick={() => toggleYearMonth(yearMonth)}>
                {formatYearMonth(yearMonth)}
                {maybeYear(yearMonth)}{" "}
                <span className={styles.eventCounter}>({eventCount})</span>
              </h2>
              {eventCount === 0 && (
                <div className={styles.calendarWeekEmpty}>
                  Ingen arrangementer denne måneden.
                </div>
              )}
              {eventCount !== 0 &&
                Object.keys(eventsByDate[yearMonth]).map((week) => (
                  <CalendarWeek
                    key={week}
                    days={eventsByDate[yearMonth][week]}
                  />
                ))}
            </div>
          );
        })}
      </div>
    </>
  );
};
