/**
 * ScheduleCalendar 유틸리티 함수
 */

import {
  startOfMonth,
  endOfMonth,
  startOfWeek,
  endOfWeek,
  startOfDay,
  eachDayOfInterval,
  isSameMonth,
  isSameDay,
  isToday,
  isBefore,
  format,
  addMonths,
  subMonths,
  parseISO,
  isWithinInterval,
  differenceInDays,
  addDays,
  getDay,
} from 'date-fns';
import { ko } from 'date-fns/locale';
import type { ScheduleEvent, DayBadge } from './types';

/**
 * 월간 달력에 표시할 날짜 배열 생성
 * (이전 달/다음 달 날짜 포함)
 */
export function getMonthDays(date: Date, weekStartsOn: 0 | 1 = 0): Date[] {
  const monthStart = startOfMonth(date);
  const monthEnd = endOfMonth(date);
  const calendarStart = startOfWeek(monthStart, { weekStartsOn });
  const calendarEnd = endOfWeek(monthEnd, { weekStartsOn });

  return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
}

/**
 * 주간 달력에 표시할 날짜 배열 생성
 */
export function getWeekDays(date: Date, weekStartsOn: 0 | 1 = 0): Date[] {
  const weekStart = startOfWeek(date, { weekStartsOn });
  const weekEnd = endOfWeek(date, { weekStartsOn });

  return eachDayOfInterval({ start: weekStart, end: weekEnd });
}

/**
 * 요일 헤더 배열 생성
 */
export function getWeekdayHeaders(weekStartsOn: 0 | 1 = 0): string[] {
  const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
  if (weekStartsOn === 1) {
    return [...weekdays.slice(1), weekdays[0]];
  }
  return weekdays;
}

/**
 * 날짜가 현재 월에 속하는지 확인
 */
export function isCurrentMonth(date: Date, currentDate: Date): boolean {
  return isSameMonth(date, currentDate);
}

/**
 * 날짜가 오늘인지 확인
 */
export function checkIsToday(date: Date): boolean {
  return isToday(date);
}

/**
 * 날짜가 오늘 이전인지 확인 (지난 일자)
 */
export function checkIsPast(date: Date): boolean {
  const today = startOfDay(new Date());
  const targetDate = startOfDay(date);
  return isBefore(targetDate, today);
}

/**
 * 두 날짜가 같은지 확인
 */
export function isSameDate(date1: Date | null, date2: Date): boolean {
  if (!date1) return false;
  return isSameDay(date1, date2);
}

/**
 * 날짜 포맷
 */
export function formatCalendarDate(date: Date, formatStr: string = 'yyyy-MM-dd'): string {
  return format(date, formatStr, { locale: ko });
}

/**
 * 년월 포맷 (예: "2025년 12월")
 */
export function formatYearMonth(date: Date): string {
  return format(date, 'yyyy년 M월', { locale: ko });
}

/**
 * 다음 달로 이동
 */
export function getNextMonth(date: Date): Date {
  return addMonths(date, 1);
}

/**
 * 이전 달로 이동
 */
export function getPrevMonth(date: Date): Date {
  return subMonths(date, 1);
}

/**
 * 특정 날짜에 해당하는 이벤트 필터링
 */
export function getEventsForDate(events: ScheduleEvent[], date: Date): ScheduleEvent[] {
  const dateStr = format(date, 'yyyy-MM-dd');

  return events.filter((event) => {
    const startDate = parseISO(event.startDate);
    const endDate = parseISO(event.endDate);
    const targetDate = parseISO(dateStr);

    return isWithinInterval(targetDate, { start: startDate, end: endDate });
  });
}

/**
 * 특정 날짜에 해당하는 뱃지 찾기
 */
export function getBadgeForDate(badges: DayBadge[], date: Date): DayBadge | undefined {
  const dateStr = format(date, 'yyyy-MM-dd');
  return badges.find((badge) => badge.date === dateStr);
}

/**
 * 이벤트가 특정 날짜에 시작하는지 확인
 */
export function isEventStart(event: ScheduleEvent, date: Date): boolean {
  return isSameDay(parseISO(event.startDate), date);
}

/**
 * 이벤트가 특정 날짜에 끝나는지 확인
 */
export function isEventEnd(event: ScheduleEvent, date: Date): boolean {
  return isSameDay(parseISO(event.endDate), date);
}

/**
 * 이벤트 기간 (일수)
 */
export function getEventDuration(event: ScheduleEvent): number {
  const start = parseISO(event.startDate);
  const end = parseISO(event.endDate);
  return differenceInDays(end, start) + 1;
}

/**
 * 주 단위로 이벤트 분할 (여러 주에 걸치는 이벤트 처리)
 */
export interface WeekEventSegment {
  event: ScheduleEvent;
  startCol: number; // 0-6 (시작 컬럼)
  colSpan: number; // 차지하는 컬럼 수
  isStart: boolean; // 이벤트 시작 주인지
  isEnd: boolean; // 이벤트 끝 주인지
}

export function getEventSegmentsForWeek(
  events: ScheduleEvent[],
  weekDays: Date[],
  weekStartsOn: 0 | 1 = 0
): WeekEventSegment[] {
  const weekStart = weekDays[0];
  const weekEnd = weekDays[6];

  const segments: WeekEventSegment[] = [];

  events.forEach((event) => {
    const eventStart = parseISO(event.startDate);
    const eventEnd = parseISO(event.endDate);

    // 이 주와 겹치는지 확인
    if (eventEnd < weekStart || eventStart > weekEnd) {
      return; // 이 주와 겹치지 않음
    }

    // 이 주에서의 시작/끝 날짜 계산
    const segmentStart = eventStart < weekStart ? weekStart : eventStart;
    const segmentEnd = eventEnd > weekEnd ? weekEnd : eventEnd;

    // 컬럼 위치 계산
    const startCol = getDay(segmentStart);
    const adjustedStartCol = weekStartsOn === 1
      ? (startCol === 0 ? 6 : startCol - 1)
      : startCol;

    const colSpan = differenceInDays(segmentEnd, segmentStart) + 1;

    segments.push({
      event,
      startCol: adjustedStartCol,
      colSpan,
      isStart: isSameDay(eventStart, segmentStart),
      isEnd: isSameDay(eventEnd, segmentEnd),
    });
  });

  return segments;
}

/**
 * 이벤트 행 배치 계산 (다른 색상은 다른 행에 배치)
 * - 같은 색상(작업반장)끼리만 같은 행 공유 가능
 * - 다른 색상은 무조건 다른 행에 배치
 * - 시작일이 빠른 색상 그룹이 위에 배치
 */
export function assignEventRows(segments: WeekEventSegment[]): Map<string, number> {
  const rowMap = new Map<string, number>();

  // 색상별로 그룹화
  const colorGroups = new Map<string, WeekEventSegment[]>();
  segments.forEach((segment) => {
    const color = segment.event.color || 'blue';
    if (!colorGroups.has(color)) {
      colorGroups.set(color, []);
    }
    colorGroups.get(color)!.push(segment);
  });

  // 색상 그룹을 가장 이른 시작일 기준으로 정렬
  const sortedColorGroups = Array.from(colorGroups.entries()).sort((a, b) => {
    // 각 그룹에서 가장 이른 시작일 찾기
    const aMinStart = Math.min(...a[1].map(s => parseISO(s.event.startDate).getTime()));
    const bMinStart = Math.min(...b[1].map(s => parseISO(s.event.startDate).getTime()));
    return aMinStart - bMinStart;
  });

  let currentBaseRow = 0;

  // 각 색상 그룹별로 행 배치 (시작일 순)
  sortedColorGroups.forEach(([, groupSegments]) => {
    const occupied: boolean[][] = [];

    // 시작 컬럼 순으로 정렬
    const sortedSegments = [...groupSegments].sort((a, b) => {
      if (a.startCol !== b.startCol) return a.startCol - b.startCol;
      return b.colSpan - a.colSpan; // 긴 이벤트 먼저
    });

    let maxRowInGroup = 0;

    sortedSegments.forEach((segment) => {
      const { event, startCol, colSpan } = segment;

      // 이 색상 그룹 내에서 가능한 가장 낮은 행 찾기
      let row = 0;
      while (true) {
        if (!occupied[row]) {
          occupied[row] = Array(7).fill(false);
        }

        // 이 행에서 해당 컬럼들이 비어있는지 확인
        let canPlace = true;
        for (let col = startCol; col < startCol + colSpan && col < 7; col++) {
          if (occupied[row][col]) {
            canPlace = false;
            break;
          }
        }

        if (canPlace) {
          // 배치
          for (let col = startCol; col < startCol + colSpan && col < 7; col++) {
            occupied[row][col] = true;
          }
          rowMap.set(event.id, currentBaseRow + row);
          maxRowInGroup = Math.max(maxRowInGroup, row);
          break;
        }

        row++;
        if (row > 10) break; // 최대 10행까지
      }
    });

    // 다음 색상 그룹은 이 그룹 아래 행부터 시작
    currentBaseRow += maxRowInGroup + 1;
  });

  return rowMap;
}

/**
 * 전역 이벤트 행 배치 계산 (월간 뷰 전체 기준)
 * - 먼저 시작한 이벤트가 끝날 때까지 같은 행 유지
 * - 나중에 시작한 이벤트는 아래 행으로 배치
 */
export function assignGlobalEventRows(events: ScheduleEvent[]): Map<string, number> {
  const rowMap = new Map<string, number>();

  if (events.length === 0) return rowMap;

  // 모든 이벤트를 시작일 순으로 정렬 (시작일 같으면 기간 긴 것 먼저)
  const sortedEvents = [...events].sort((a, b) => {
    const aStart = parseISO(a.startDate).getTime();
    const bStart = parseISO(b.startDate).getTime();
    if (aStart !== bStart) return aStart - bStart;
    const aDuration = parseISO(a.endDate).getTime() - aStart;
    const bDuration = parseISO(b.endDate).getTime() - bStart;
    return bDuration - aDuration;
  });

  // 각 행의 점유 종료일 추적
  const rowEndDates: number[] = [];

  sortedEvents.forEach((event) => {
    const eventStart = parseISO(event.startDate).getTime();
    const eventEnd = parseISO(event.endDate).getTime();

    // 이 이벤트를 배치할 수 있는 가장 낮은 행 찾기
    let row = 0;
    while (row < rowEndDates.length) {
      // 이전 이벤트가 끝났으면 배치 가능
      if (rowEndDates[row] < eventStart) {
        break;
      }
      row++;
    }

    // 행에 배치하고 종료일 업데이트
    rowEndDates[row] = eventEnd;
    rowMap.set(event.id, row);
  });

  return rowMap;
}

/**
 * 다음 날로 이동
 */
export function getNextDay(date: Date): Date {
  return addDays(date, 1);
}

/**
 * 이전 날로 이동
 */
export function getPrevDay(date: Date): Date {
  return addDays(date, -1);
}

/**
 * 다음 주로 이동
 */
export function getNextWeek(date: Date): Date {
  return addDays(date, 7);
}

/**
 * 이전 주로 이동
 */
export function getPrevWeek(date: Date): Date {
  return addDays(date, -7);
}

/**
 * 년월일 포맷 (예: "2026년 2월 2일 (월)")
 */
export function formatYearMonthDay(date: Date): string {
  return format(date, 'yyyy년 M월 d일 (EEE)', { locale: ko });
}

/**
 * 월간 뷰에서 주 단위로 날짜 분할
 */
export function splitIntoWeeks(days: Date[]): Date[][] {
  const weeks: Date[][] = [];
  for (let i = 0; i < days.length; i += 7) {
    weeks.push(days.slice(i, i + 7));
  }
  return weeks;
}
