import { cn } from "@/lib/utils";
import { Calendar } from "@/components/ui/calendar";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Popover, PopoverTrigger } from "@/components/ui/popover";
import { isSameDay, getWeeksInMonth, getWeekOfMonth, eachDayOfInterval, parse, startOfMonth, startOfWeek, endOfWeek, endOfMonth, format, isSameMonth } from "date-fns";
import { SchedulePopover } from "../schedule/SchedulePopover";
import { ScheduleNetwork } from "@/network/ScheduleNetwork";
import { ScheduleListData } from "@/data/response";
import { CustomCalendarCN } from "./CustomCalendarCN";
import { toast } from "sonner";
import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode";
import { SchedulerDTO as DTO, Type } from '@baekyangdan/core-utils';
interface CustomCalendarProps {
  data?: any;
}

interface EventBarPosition extends DTO.ScheduleList {
  positionStyle: React.CSSProperties;
  trackIndex: number;
  isOverflow?: boolean;
  segmentId: string;
}

const TRACK_HEIGHT = 20;
const TOP_OFFSET_FROM_CELL = 35;
const DATE_FORMAT_ARIA = 'EEEE, MMMM do, yyyy';
const DATE_FORMAT_KEY = 'yyyyMMdd';

export const CustomCalendar = ({ data }: CustomCalendarProps) => {
  const [isLoading, setIsLoading] = useState(false);
  const [refetchTrigger, setRefetchTrigger] = useState(0);
  const [weekCount, setWeekCount] = useState(5);
  const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
  const [popoverOpen, setPopoverOpen] = useState(false);
  const [popoverSide, setPopoverSide] = useState<'right' | 'left'>('right');
  const [popoverAlign, setPopoverAlign] = useState<'start' | 'end'>('end');
  const [popoverMode, setPopoverMode] = useState<SchedulePopoverMode>('list');
  const [popoverDetailId, setPopoverDetailId] = useState('');
  const [month, setMonth] = useState(new Date());
  const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });
  const [maxVisibleEvents, setMaxVisibleEvents] = useState(3);
  const [overflowTrackIndex, setOverflowTrackIndex] = useState(maxVisibleEvents + 1);
  const [scheduleList, setScheduleList] = useState<Array<DTO.ScheduleList>>([]);
  const [barPositions, setBarPositions] = useState<Array<EventBarPosition>>([]);
  const scheduleNetwork = new ScheduleNetwork();
  const containerRef = useRef<HTMLDivElement>(null);
  const cellInfoMapRef = useRef<Map<string, { cell: HTMLElement, rect: DOMRect }>>(new Map());

  const updateWeekCount = () => {
    if (containerRef === null) return;
    if (!containerRef.current) return;
    const weeks = containerRef.current.querySelectorAll('.rdp-week');

    if (weeks?.length) setWeekCount(weeks.length);
  }

  useEffect(() => {
    updateWeekCount();
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
      if (window.innerHeight >= 850) {
        setMaxVisibleEvents(3);
        setOverflowTrackIndex(4);
      } else {
        setMaxVisibleEvents(2);
        setOverflowTrackIndex(3);
      }
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    }
  }, []);

  // 화면 상의 달이 바뀌면 req 하는 로직
  useLayoutEffect(() => {
    updateWeekCount();

    const reqList = async () => {
      setIsLoading(true);
      const requestedMonth = month;
      
      const monthStart = startOfMonth(month);
      const monthEnd = endOfMonth(month);

      const startDate = startOfWeek(monthStart, { weekStartsOn: 0 });
      const endDate = endOfWeek(monthEnd, { weekStartsOn: 0 });

      const data = {
        startDate: startDate,
        endDate: endDate
      } as DTO.ScheduleListRequest;

      const result = await scheduleNetwork.getList(data);

      if (result.success) {
        if (result.data) {
          if (isSameMonth(requestedMonth, month)) {
            // setCurrentDataMonth(month);
            setScheduleList(result.data);
          }
          // setScheduleList(result.data.data);
        }
      }
      setIsLoading(false);
    }

    requestAnimationFrame(() => {
      const reqListPromise = reqList;

      toast.promise(
        reqListPromise,
        {
          loading: `${month.getFullYear()}년 ${month.getMonth() + 1}월 일정을 불러오는 중입니다`,
          success: `일정을 불러왔습니다.`,
          error: `일정을 불러오는 데에 실패하였습니다.\n잠시 후 다시 시도해주십시오.`,
          duration: 1000
        },
      )
      updateWeekCount();
    });
  }, [month, refetchTrigger]);

  const refetchList = () => {
    setRefetchTrigger(prev => prev + 1);
  }

  // 이벤트 bar 그리는 로직
  useLayoutEffect(() => {
    // if (!isSameMonth(month, currentDataMonth)) {
    //   setBarPositions([]);
    //   return;
    // }

    if (!containerRef.current || scheduleList.length === 0) {
      // setBarPositions([]);
      return;
    }
    
    // setBarPositions([]);
    const containerRect = containerRef.current.getBoundingClientRect();
    const dayCells = containerRef.current.querySelectorAll('.rdp-day button');

    const cellInfoMap = new Map<string, { cell: HTMLElement, rect: DOMRect }>();

    dayCells.forEach((cell) => {
      const dayButton = cell as HTMLButtonElement;
      let ariaLabel = dayButton.getAttribute('aria-label');
      if (ariaLabel) {
        try {
          if (ariaLabel.startsWith('Today, ')) {
            ariaLabel = ariaLabel.replace('Today, ', '');
          }
          if (ariaLabel.endsWith(', selected')) {
            ariaLabel = ariaLabel.replace(', selected', '');
          }
          const parsedDate = parse(ariaLabel, DATE_FORMAT_ARIA, new Date());
          
          if (!isNaN(parsedDate.getTime())) {
            const dateKey = format(parsedDate, DATE_FORMAT_KEY);
            console.log(dateKey);
            cellInfoMap.set(dateKey, {
              cell: dayButton,
              rect: dayButton.getBoundingClientRect()
            });

          }
        } catch (e) {}
      }
    });
    cellInfoMapRef.current = cellInfoMap;

    const scheduleListWithTrack: (ScheduleListData & { trackIndex: number })[] = [];
    const occupiedTrackList = new Map<string, number[]>();
    const overflowCountMap = new Map<string, number>();

    const sortedScheduleList = [...scheduleList].sort((a, b) => 
      new Date(b.endDate).getTime() - new Date(b.startDate).getTime() - 
      (new Date(a.endDate).getTime() - new Date(a.startDate).getTime())
    );

    sortedScheduleList.forEach(schedule => {
      const startDateObj = new Date(schedule.startDate);
      const endDateObj = new Date(schedule.endDate);

      const eventDays = eachDayOfInterval({ start: startDateObj, end: endDateObj });

      let assignedTrack = -1;

      for (let track = 0; track < maxVisibleEvents; track++) {
        let isAvailable = true;
        for (const day of eventDays) {
          const dayKey = format(day, DATE_FORMAT_KEY);
          if (cellInfoMap.has(dayKey)) {
            const occupied = occupiedTrackList.get(dayKey) || [];

            if (occupied.includes(track)) {
              isAvailable = false;
              break;
            }
          }
        }

        if (isAvailable) {
          assignedTrack = track;
          break;
        }
      }

      if (assignedTrack !== -1) {
        for (const day of eventDays) {
          const dayKey = format(day, DATE_FORMAT_KEY);
          if (cellInfoMap.has(dayKey)) {
            const occupied = occupiedTrackList.get(dayKey) || [];
            occupiedTrackList.set(dayKey, [...occupied, assignedTrack]);
          }
        }
        scheduleListWithTrack.push({ 
          ...schedule,
          startDate: schedule.startDate.toISOString(),
          endDate: schedule.endDate.toISOString(),
          trackIndex: assignedTrack });
      } else {
        for (const day of eventDays) {
          const dayKey = format(day, DATE_FORMAT_KEY);
          if (cellInfoMap.has(dayKey)) {
            overflowCountMap.set(dayKey, (overflowCountMap.get(dayKey) || 0) + 1);
          }
        }
      }
    });

    const regularPositions: EventBarPosition[] = [];

    // 달력을 초과한 부분에 대한 처리
    const visibleDayKeys = Array.from(cellInfoMap.keys());
    if (!visibleDayKeys || visibleDayKeys.length === 0) return;

    const calendarStart = parse(visibleDayKeys.at(0)!, DATE_FORMAT_KEY, new Date());
    const calendarEnd = parse(visibleDayKeys.at(-1)!, DATE_FORMAT_KEY, new Date());

    scheduleListWithTrack.forEach(schedule => {
      const startDateObj = new Date(schedule.startDate);
      const endDateObj = new Date(schedule.endDate);

      const renderStartDate = startDateObj > calendarStart ? startDateObj : calendarStart;
      const renderEndDate = endDateObj < calendarEnd ? endDateObj : calendarEnd;

      const renderStartKey = format(renderStartDate, DATE_FORMAT_KEY);
      const renderEndKey = format(renderEndDate, DATE_FORMAT_KEY);

      const allRenderDays = eachDayOfInterval({ start: renderStartDate, end: renderEndDate });

      let segmentStartDay = renderStartDate;

      allRenderDays.forEach((currentDay, index) => {
        const dayOfWeek = currentDay.getDay();
        const isLastDayOfEvent = index === allRenderDays.length - 1;

        if (dayOfWeek === 6 && !isLastDayOfEvent) {
          const segmentEndDay = currentDay;

          createAndPushPosition(
            regularPositions, schedule,
            segmentStartDay, segmentEndDay,
            cellInfoMap, containerRect
          );

          segmentStartDay = new Date(currentDay);
          segmentStartDay.setDate(segmentStartDay.getDate() + 1);
        } else if (isLastDayOfEvent) {
          createAndPushPosition(
            regularPositions, schedule,
            segmentStartDay, currentDay,
            cellInfoMap, containerRect
          );
        }
      });
    });

    const overflowPositions: EventBarPosition[] = [];
    overflowCountMap.forEach((count, dayKey) => {
      const dayInfo = cellInfoMap.get(dayKey);

      if (dayInfo) {
        const dayRect = dayInfo.rect;

        const baseTop = dayRect.top - containerRect.top;
        const top = baseTop + TOP_OFFSET_FROM_CELL + ((overflowTrackIndex - 1) * (TRACK_HEIGHT + 5));
        const left = dayRect.left - containerRect.left + 5;
        const width = dayInfo.cell.clientWidth - 10;

        overflowPositions.push({
          id: `overflow-${dayKey}`,
          name: `${count} more`,
          startDate: new Date(),
          endDate: new Date(),
          style: '#9CA3AF',
          trackIndex: overflowTrackIndex,
          isOverflow: true,
          segmentId: dayKey,
          positionStyle: {
            top: `${top}px`,
            left: `${left}px`,
            width: `${width}px`,
            height: `${TRACK_HEIGHT}px`
          }
        } as EventBarPosition)
      }
    });
    setBarPositions([...regularPositions, ...overflowPositions]);
  }, [scheduleList, month, windowSize]);

  const createAndPushPosition = (
    positions: EventBarPosition[],
    schedule: ScheduleListData & { trackIndex: number },
    segmentStartDay: Date,
    segmentEndDay: Date,
    cellInfoMap: Map<string, { cell: HTMLElement, rect: DOMRect }>,
    containerRect: DOMRect
  ) => {
    const renderStartKey = format(segmentStartDay, DATE_FORMAT_KEY);
    const renderEndKey = format(segmentEndDay, DATE_FORMAT_KEY);

    const startInfo = cellInfoMap.get(renderStartKey);
    const endInfo = cellInfoMap.get(renderEndKey);

    if (startInfo && endInfo) {
      const startRect = startInfo.rect;
      const endRect = endInfo.rect;

      const baseTop = startRect.top - containerRect.top;
      const top = baseTop + TOP_OFFSET_FROM_CELL + (schedule.trackIndex * (TRACK_HEIGHT + 5));

      const left = startRect.left - containerRect.left + 5;
      const width = endRect.right - startRect.left - 10;

      positions.push({
        ...schedule,
        type: schedule.type as Type.Type,
        status: schedule.status as Type.Status,
        startDate: new Date(schedule.startDate),
        endDate: new Date(schedule.endDate),
        trackIndex: schedule.trackIndex,
        id: schedule.id,
        segmentId: `${schedule.id}-${renderStartKey}`,
        positionStyle: {
          top: `${top}px`,
          left: `${left}px`,
          width: `${width}px`,
          height: `${TRACK_HEIGHT}px`
        }
      })
    }
  }

  const handleMonthChange = async (month: Date) => {
    if (isLoading) return;
    setMonth(month);
  }

  const handleOpenChange = (open: boolean) => {
    setPopoverOpen(open);
    if (!open) {
      setTimeout(() => {
        setSelectedDate(undefined);
      }, 150);
    }
  }

  const handleDaySelect = (date: Date | undefined) => {
    if (!date) {
      setPopoverOpen(false);
      setTimeout(() => {
        setSelectedDate(undefined);
        setPopoverDetailId('');
        setPopoverMode('list');
      }, 150);
      return;
    }
    if (date) {
      setSelectedDate(date);

      const dayOfWeek = date.getDay();

      if (0 <= dayOfWeek && dayOfWeek < 4) {
        setPopoverSide('right');
      } else {
        setPopoverSide('left');
      }

      const options = { weekStartsOn: 0 as 0 };

      const totalWeeks = getWeeksInMonth(date, options);

      const currentWeekNumber = getWeekOfMonth(date, options);

      const threshold = Math.ceil(totalWeeks / 2);

      if (currentWeekNumber <= threshold) {
        setPopoverAlign('start');
      } else {
        setPopoverAlign('end');
      }

      requestAnimationFrame(() => {
        setPopoverOpen(true);
      })
    }
  }

  const findDateFromClick = (
    clientX: number,
    clientY: number
  ): string | null => {
    if (!cellInfoMapRef.current) {
      return null;
    }
    const cellInfoMap = cellInfoMapRef.current;

    for (const [dateKey, info] of cellInfoMap.entries()) {
      const rect = info.rect;

      if (
        clientX >= rect.left
        && clientX < rect.right
        && clientY >= rect.top
        && clientY < rect.bottom
      ) {
        return dateKey;
      }
    }

    return null;
  }

  const handleEventBarClick = (e: React.MouseEvent<HTMLDivElement>, bar: EventBarPosition) => {
    const clientX = e.clientX;
    const clientY = e.clientY;
    const eventId = bar.id;

    const dateKey = findDateFromClick(clientX, clientY);

    if (dateKey && !selectedDate) {
      const clickedDate = parse(dateKey, DATE_FORMAT_KEY, new Date());
      if (!(eventId.includes('overflow'))) {
        setPopoverMode('detail');
        setPopoverDetailId(eventId);
      }
      handleDaySelect(clickedDate);
    } else {
      handleDaySelect(undefined);

    }
  }

  return (
    <div
      className="w-full h-full relative"
      ref={containerRef}
    >
      <Popover
        open={popoverOpen}
        onOpenChange={handleOpenChange}
      >
        <Calendar 
          mode="single"
          className="h-full w-full border border-indigo-200 rounded-lg shadow-sm shadow-indigo-200"
          selected={selectedDate}
          onSelect={handleDaySelect}
          month={month}
          onMonthChange={handleMonthChange}
          classNames={CustomCalendarCN}
          styles={{
            day: {
              height: `calc(100%/${weekCount})`
            },
          }}
          components={{
            Day: ({ day, ...props }) => {
              const date = day.date;
              const isSelected = selectedDate && isSameDay(selectedDate, date);
              return (
                <td {...props}>
                  { isSelected
                    ? <PopoverTrigger asChild>
                      {props.children}
                    </PopoverTrigger>
                    : props.children
                  }
                </td>
              )
            },
            DayButton: ({ day, ...props}) => (
              <button
                {...props}
              >
                {props.children}
              </button>
            )
          }}
        />
        {
          barPositions.map(pos => (
            <div
              onClick={(e: React.MouseEvent<HTMLDivElement>) => handleEventBarClick(e, pos)}
              key={pos.segmentId}
              id={pos.segmentId}
              className={cn(
                `flex flex-row justify-start items-center absolute select-none`,
                "py-0.5 px-2 rounded-sm text-xs text-white overflow-hidden"
              )}
              style={{...pos.positionStyle, backgroundColor: pos.style}}
            >
              {pos.name}
            </div>
          ))
        }
        <SchedulePopover 
          date={selectedDate}
          open={popoverOpen}
          mode={popoverMode}
          setMode={setPopoverMode}
          detailId={popoverDetailId}
          setDetailId={setPopoverDetailId}
          popoverSide={popoverSide}
          popoverAlign={popoverAlign}
          onScheduleCreated={refetchList}
        />
        
      </Popover>
    </div>
  )
}