"use client";

/**
 * TimelineScheduler — 하드코딩 페이지용 공통 타임라인 스케줄러 컴포넌트
 *
 * 기능:
 * - 리소스(설비/품목) 기준 Y축, 날짜 기준 X축
 * - 줌 레벨 전환 (일/주/월)
 * - 날짜 네비게이션 (이전/다음/오늘)
 * - 이벤트 바 드래그 이동
 * - 이벤트 바 리사이즈 (좌/우 핸들)
 * - 오늘 날짜 빨간 세로선
 * - 진행률 바 시각화
 * - 마일스톤 (다이아몬드) 표시
 * - 상태별 색상 + 범례
 * - 충돌 감지 (같은 리소스에서 겹침 시 빨간 테두리)
 */

import React, { useState, useRef, useCallback, useMemo, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
  ChevronLeft,
  ChevronRight,
  CalendarDays,
  Loader2,
  Diamond,
} from "lucide-react";
import { cn } from "@/lib/utils";

// ─── 타입 정의 ───

export interface TimelineResource {
  id: string;
  label: string;
  subLabel?: string;
}

export interface TimelineEvent {
  id: string | number;
  resourceId: string;
  startDate: string; // YYYY-MM-DD
  endDate: string;   // YYYY-MM-DD
  label?: string;
  status?: string;
  progress?: number; // 0~100
  isMilestone?: boolean;
  data?: any;
}

export type ZoomLevel = "day" | "week" | "month";

export interface StatusColor {
  key: string;
  label: string;
  bgClass: string; // tailwind gradient class e.g. "from-blue-500 to-blue-600"
}

export interface TimelineSchedulerProps {
  resources: TimelineResource[];
  events: TimelineEvent[];
  /** 타임라인 시작 기준일 (기본: 오늘) */
  startDate?: Date;
  /** 줌 레벨 (기본: week) */
  zoomLevel?: ZoomLevel;
  onZoomChange?: (zoom: ZoomLevel) => void;
  /** 이벤트 바 클릭 */
  onEventClick?: (event: TimelineEvent) => void;
  /** 드래그 이동 완료 */
  onEventMove?: (eventId: string | number, newStartDate: string, newEndDate: string) => void;
  /** 리사이즈 완료 */
  onEventResize?: (eventId: string | number, newStartDate: string, newEndDate: string) => void;
  /** 상태별 색상 배열 */
  statusColors?: StatusColor[];
  /** 진행률 바 표시 여부 */
  showProgress?: boolean;
  /** 마일스톤 표시 여부 */
  showMilestones?: boolean;
  /** 오늘 세로선 표시 */
  showTodayLine?: boolean;
  /** 범례 표시 */
  showLegend?: boolean;
  /** 충돌 감지 */
  conflictDetection?: boolean;
  /** 로딩 상태 */
  loading?: boolean;
  /** 데이터 없을 때 메시지 */
  emptyMessage?: string;
  /** 데이터 없을 때 아이콘 */
  emptyIcon?: React.ReactNode;
  /** 리소스 열 너비 (px) */
  resourceWidth?: number;
  /** 행 높이 (px) */
  rowHeight?: number;
}

// ─── 기본값 ───

const DEFAULT_STATUS_COLORS: StatusColor[] = [
  { key: "planned", label: "계획", bgClass: "from-blue-500 to-blue-600" },
  { key: "work-order", label: "지시", bgClass: "from-amber-500 to-amber-600" },
  { key: "in-progress", label: "진행", bgClass: "from-emerald-500 to-emerald-600" },
  { key: "completed", label: "완료", bgClass: "from-gray-400 to-gray-500" },
];

const ZOOM_CONFIG: Record<ZoomLevel, { cellWidth: number; spanDays: number; navStep: number }> = {
  day: { cellWidth: 60, spanDays: 28, navStep: 7 },
  week: { cellWidth: 36, spanDays: 56, navStep: 14 },
  month: { cellWidth: 16, spanDays: 90, navStep: 30 },
};

// ─── 유틸리티 함수 ───

/** YYYY-MM-DD 문자열로 변환 */
function toDateStr(d: Date): string {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

/** 날짜 문자열을 Date로 (시간 0시) */
function parseDate(s: string): Date {
  const [y, m, d] = s.split("T")[0].split("-").map(Number);
  return new Date(y, m - 1, d);
}

/** 두 날짜 사이의 일 수 차이 */
function diffDays(a: Date, b: Date): number {
  return Math.round((b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24));
}

/** 날짜에 일 수 더하기 */
function addDays(d: Date, n: number): Date {
  const r = new Date(d);
  r.setDate(r.getDate() + n);
  return r;
}

function isWeekend(d: Date): boolean {
  return d.getDay() === 0 || d.getDay() === 6;
}

function isSameDay(a: Date, b: Date): boolean {
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}

const DAY_NAMES = ["일", "월", "화", "수", "목", "금", "토"];
const MONTH_NAMES = ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"];

// ─── 충돌 감지 ───

function detectConflicts(events: TimelineEvent[]): Set<string | number> {
  const conflictIds = new Set<string | number>();
  const byResource = new Map<string, TimelineEvent[]>();

  for (const ev of events) {
    if (ev.isMilestone) continue;
    if (!byResource.has(ev.resourceId)) byResource.set(ev.resourceId, []);
    byResource.get(ev.resourceId)!.push(ev);
  }

  for (const [, resEvents] of byResource) {
    for (let i = 0; i < resEvents.length; i++) {
      for (let j = i + 1; j < resEvents.length; j++) {
        const a = resEvents[i];
        const b = resEvents[j];
        const aStart = parseDate(a.startDate).getTime();
        const aEnd = parseDate(a.endDate).getTime();
        const bStart = parseDate(b.startDate).getTime();
        const bEnd = parseDate(b.endDate).getTime();
        if (aStart <= bEnd && bStart <= aEnd) {
          conflictIds.add(a.id);
          conflictIds.add(b.id);
        }
      }
    }
  }

  return conflictIds;
}

// ─── 메인 컴포넌트 ───

export default function TimelineScheduler({
  resources,
  events,
  startDate: propStartDate,
  zoomLevel: propZoom,
  onZoomChange,
  onEventClick,
  onEventMove,
  onEventResize,
  statusColors = DEFAULT_STATUS_COLORS,
  showProgress = true,
  showMilestones = true,
  showTodayLine = true,
  showLegend = true,
  conflictDetection = true,
  loading = false,
  emptyMessage = "데이터가 없습니다",
  emptyIcon,
  resourceWidth = 160,
  rowHeight = 48,
}: TimelineSchedulerProps) {
  // ── 상태 ──
  const [zoom, setZoom] = useState<ZoomLevel>(propZoom || "week");
  const [baseDate, setBaseDate] = useState<Date>(() => {
    const d = propStartDate || new Date();
    d.setHours(0, 0, 0, 0);
    return d;
  });

  // 드래그/리사이즈 상태
  const [dragState, setDragState] = useState<{
    eventId: string | number;
    mode: "move" | "resize-left" | "resize-right";
    origStartDate: string;
    origEndDate: string;
    startX: number;
    currentOffsetDays: number;
  } | null>(null);

  const gridRef = useRef<HTMLDivElement>(null);
  const scrollRef = useRef<HTMLDivElement>(null);

  // 줌 레벨 동기화
  useEffect(() => {
    if (propZoom && propZoom !== zoom) setZoom(propZoom);
  }, [propZoom]);

  const config = ZOOM_CONFIG[zoom];
  const today = useMemo(() => {
    const d = new Date();
    d.setHours(0, 0, 0, 0);
    return d;
  }, []);

  // 날짜 배열 생성
  const dates = useMemo(() => {
    const arr: Date[] = [];
    for (let i = 0; i < config.spanDays; i++) {
      arr.push(addDays(baseDate, i));
    }
    return arr;
  }, [baseDate, config.spanDays]);

  const totalWidth = config.cellWidth * config.spanDays;

  // 충돌 ID 집합
  const conflictIds = useMemo(() => {
    return conflictDetection ? detectConflicts(events) : new Set<string | number>();
  }, [events, conflictDetection]);

  // 리소스별 이벤트 그룹
  const eventsByResource = useMemo(() => {
    const map = new Map<string, TimelineEvent[]>();
    for (const r of resources) map.set(r.id, []);
    for (const ev of events) {
      if (!map.has(ev.resourceId)) map.set(ev.resourceId, []);
      map.get(ev.resourceId)!.push(ev);
    }
    return map;
  }, [resources, events]);

  // 같은 리소스 내 겹치는 이벤트들의 행(lane) 계산
  const eventLanes = useMemo(() => {
    const laneMap = new Map<string | number, number>();
    for (const [, resEvents] of eventsByResource) {
      // 시작일 기준 정렬
      const sorted = [...resEvents].sort(
        (a, b) => parseDate(a.startDate).getTime() - parseDate(b.startDate).getTime()
      );
      const lanes: { endTime: number }[] = [];
      for (const ev of sorted) {
        if (ev.isMilestone) {
          laneMap.set(ev.id, 0);
          continue;
        }
        const evStart = parseDate(ev.startDate).getTime();
        const evEnd = parseDate(ev.endDate).getTime();
        let placed = false;
        for (let l = 0; l < lanes.length; l++) {
          if (evStart > lanes[l].endTime) {
            lanes[l].endTime = evEnd;
            laneMap.set(ev.id, l);
            placed = true;
            break;
          }
        }
        if (!placed) {
          laneMap.set(ev.id, lanes.length);
          lanes.push({ endTime: evEnd });
        }
      }
    }
    return laneMap;
  }, [eventsByResource]);

  // 리소스별 최대 lane 수 -> 행 높이 결정
  const resourceLaneCounts = useMemo(() => {
    const map = new Map<string, number>();
    for (const [resId, resEvents] of eventsByResource) {
      let maxLane = 0;
      for (const ev of resEvents) {
        const lane = eventLanes.get(ev.id) || 0;
        maxLane = Math.max(maxLane, lane);
      }
      map.set(resId, resEvents.length > 0 ? maxLane + 1 : 1);
    }
    return map;
  }, [eventsByResource, eventLanes]);

  // ── 줌/네비게이션 핸들러 ──

  const handleZoom = useCallback(
    (z: ZoomLevel) => {
      setZoom(z);
      onZoomChange?.(z);
    },
    [onZoomChange]
  );

  const handleNavPrev = useCallback(() => {
    setBaseDate((prev) => addDays(prev, -config.navStep));
  }, [config.navStep]);

  const handleNavNext = useCallback(() => {
    setBaseDate((prev) => addDays(prev, config.navStep));
  }, [config.navStep]);

  const handleNavToday = useCallback(() => {
    const d = new Date();
    d.setHours(0, 0, 0, 0);
    setBaseDate(d);
  }, []);

  // ── 이벤트 바 위치 계산 ──

  const getBarStyle = useCallback(
    (startDateStr: string, endDateStr: string) => {
      const evStart = parseDate(startDateStr);
      const evEnd = parseDate(endDateStr);
      const firstDate = dates[0];
      const lastDate = dates[dates.length - 1];

      // 완전히 범위 밖이면 표시하지 않음
      if (evEnd < firstDate || evStart > lastDate) return null;

      const startIdx = Math.max(0, diffDays(firstDate, evStart));
      const endIdx = Math.min(config.spanDays - 1, diffDays(firstDate, evEnd));
      const left = startIdx * config.cellWidth;
      const width = (endIdx - startIdx + 1) * config.cellWidth;

      return { left, width };
    },
    [dates, config.cellWidth, config.spanDays]
  );

  // ── 드래그/리사이즈 핸들러 ──

  const handleMouseDown = useCallback(
    (
      e: React.MouseEvent,
      eventId: string | number,
      mode: "move" | "resize-left" | "resize-right",
      startDate: string,
      endDate: string
    ) => {
      e.preventDefault();
      e.stopPropagation();
      setDragState({
        eventId,
        mode,
        origStartDate: startDate,
        origEndDate: endDate,
        startX: e.clientX,
        currentOffsetDays: 0,
      });
    },
    []
  );

  // mousemove / mouseup (document-level)
  useEffect(() => {
    if (!dragState) return;

    const handleMouseMove = (e: MouseEvent) => {
      const dx = e.clientX - dragState.startX;
      const dayOffset = Math.round(dx / config.cellWidth);
      setDragState((prev) => (prev ? { ...prev, currentOffsetDays: dayOffset } : null));
    };

    const handleMouseUp = (e: MouseEvent) => {
      if (!dragState) return;
      const dx = e.clientX - dragState.startX;
      const dayOffset = Math.round(dx / config.cellWidth);

      if (dayOffset !== 0) {
        const origStart = parseDate(dragState.origStartDate);
        const origEnd = parseDate(dragState.origEndDate);

        if (dragState.mode === "move") {
          const newStart = toDateStr(addDays(origStart, dayOffset));
          const newEnd = toDateStr(addDays(origEnd, dayOffset));
          onEventMove?.(dragState.eventId, newStart, newEnd);
        } else if (dragState.mode === "resize-left") {
          const newStart = toDateStr(addDays(origStart, dayOffset));
          const newEnd = dragState.origEndDate.split("T")[0];
          // 시작이 종료를 넘지 않도록
          if (parseDate(newStart) <= parseDate(newEnd)) {
            onEventResize?.(dragState.eventId, newStart, newEnd);
          }
        } else if (dragState.mode === "resize-right") {
          const newStart = dragState.origStartDate.split("T")[0];
          const newEnd = toDateStr(addDays(origEnd, dayOffset));
          if (parseDate(newStart) <= parseDate(newEnd)) {
            onEventResize?.(dragState.eventId, newStart, newEnd);
          }
        }
      }

      setDragState(null);
    };

    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);
    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [dragState, config.cellWidth, onEventMove, onEventResize]);

  // 드래그 중인 이벤트의 현재 표시 위치 계산
  const getDraggedBarStyle = useCallback(
    (event: TimelineEvent) => {
      if (!dragState || dragState.eventId !== event.id) return null;

      const origStart = parseDate(dragState.origStartDate);
      const origEnd = parseDate(dragState.origEndDate);
      const offset = dragState.currentOffsetDays;

      let newStart: Date, newEnd: Date;
      if (dragState.mode === "move") {
        newStart = addDays(origStart, offset);
        newEnd = addDays(origEnd, offset);
      } else if (dragState.mode === "resize-left") {
        newStart = addDays(origStart, offset);
        newEnd = origEnd;
        if (newStart > newEnd) newStart = newEnd;
      } else {
        newStart = origStart;
        newEnd = addDays(origEnd, offset);
        if (newEnd < newStart) newEnd = newStart;
      }

      return getBarStyle(toDateStr(newStart), toDateStr(newEnd));
    },
    [dragState, getBarStyle]
  );

  // ── 오늘 라인 위치 ──

  const todayLineLeft = useMemo(() => {
    if (!showTodayLine || dates.length === 0) return null;
    const firstDate = dates[0];
    const lastDate = dates[dates.length - 1];
    if (today < firstDate || today > lastDate) return null;
    const idx = diffDays(firstDate, today);
    return idx * config.cellWidth + config.cellWidth / 2;
  }, [dates, today, config.cellWidth, showTodayLine]);

  // ── 상태 색상 매핑 ──

  const getStatusColor = useCallback(
    (status?: string) => {
      if (!status) return statusColors[0]?.bgClass || "from-blue-500 to-blue-600";
      const found = statusColors.find((c) => c.key === status);
      return found?.bgClass || statusColors[0]?.bgClass || "from-blue-500 to-blue-600";
    },
    [statusColors]
  );

  // ── 날짜 헤더 그룹 ──

  const dateGroups = useMemo(() => {
    if (zoom === "day") {
      return null; // day 뷰에서는 상위 그룹 없이 바로 날짜 표시
    }

    // week / month 뷰: 월 단위로 그룹
    const groups: { label: string; span: number; startIdx: number }[] = [];
    let currentMonth = -1;
    let currentYear = -1;

    for (let i = 0; i < dates.length; i++) {
      const d = dates[i];
      if (d.getMonth() !== currentMonth || d.getFullYear() !== currentYear) {
        groups.push({
          label: `${d.getFullYear()}년 ${MONTH_NAMES[d.getMonth()]}`,
          span: 1,
          startIdx: i,
        });
        currentMonth = d.getMonth();
        currentYear = d.getFullYear();
      } else {
        groups[groups.length - 1].span++;
      }
    }
    return groups;
  }, [dates, zoom]);

  // ── 렌더링 ──

  if (loading) {
    return (
      <div className="flex items-center justify-center py-16">
        <Loader2 className="h-8 w-8 animate-spin text-primary" />
      </div>
    );
  }

  if (resources.length === 0 || events.length === 0) {
    return (
      <div className="flex flex-col items-center justify-center py-16 text-muted-foreground">
        {emptyIcon}
        <p className="text-base font-medium mb-2 mt-3">{emptyMessage}</p>
      </div>
    );
  }

  const barHeight = 24;
  const barGap = 2;

  return (
    <div className="flex flex-col gap-3">
      {/* 컨트롤 바 */}
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div className="flex items-center gap-2">
          <Button size="sm" variant="outline" onClick={handleNavPrev}>
            <ChevronLeft className="h-4 w-4" />
          </Button>
          <Button size="sm" variant="outline" onClick={handleNavToday}>
            <CalendarDays className="mr-1 h-4 w-4" />
            오늘
          </Button>
          <Button size="sm" variant="outline" onClick={handleNavNext}>
            <ChevronRight className="h-4 w-4" />
          </Button>
          <span className="text-xs text-muted-foreground ml-2">
            {toDateStr(dates[0])} ~ {toDateStr(dates[dates.length - 1])}
          </span>
        </div>
        <div className="flex items-center gap-1">
          {(["day", "week", "month"] as ZoomLevel[]).map((z) => (
            <Button
              key={z}
              size="sm"
              variant={zoom === z ? "default" : "outline"}
              className="h-7 text-xs px-3"
              onClick={() => handleZoom(z)}
            >
              {z === "day" ? "일" : z === "week" ? "주" : "월"}
            </Button>
          ))}
        </div>
      </div>

      {/* 범례 */}
      {showLegend && (
        <div className="flex items-center gap-4 flex-wrap text-xs">
          <span className="font-semibold text-muted-foreground">상태:</span>
          {statusColors.map((sc) => (
            <div key={sc.key} className="flex items-center gap-1.5">
              <div className={cn("h-3.5 w-5 rounded bg-gradient-to-br", sc.bgClass)} />
              <span>{sc.label}</span>
            </div>
          ))}
          {showMilestones && (
            <div className="flex items-center gap-1.5">
              <Diamond className="h-3.5 w-3.5 text-purple-500 fill-purple-500" />
              <span>마일스톤</span>
            </div>
          )}
          {conflictDetection && (
            <div className="flex items-center gap-1.5">
              <div className="h-3.5 w-5 rounded border-2 border-red-500 bg-red-500/20" />
              <span>충돌</span>
            </div>
          )}
        </div>
      )}

      {/* 타임라인 본체 */}
      <div className="rounded-lg border bg-background overflow-hidden">
        <div
          ref={scrollRef}
          className="overflow-x-auto overflow-y-auto"
          style={{ maxHeight: "calc(100vh - 350px)" }}
        >
          <div className="flex" style={{ minWidth: resourceWidth + totalWidth }}>
            {/* 좌측: 리소스 라벨 */}
            <div
              className="shrink-0 border-r bg-muted/30 z-20 sticky left-0"
              style={{ width: resourceWidth }}
            >
              {/* 헤더 공간 */}
              <div
                className="border-b bg-muted/50 flex items-center justify-center text-xs font-semibold text-muted-foreground"
                style={{ height: dateGroups ? 60 : 36 }}
              >
                리소스
              </div>
              {/* 리소스 행 */}
              {resources.map((res) => {
                const laneCount = resourceLaneCounts.get(res.id) || 1;
                const h = Math.max(rowHeight, laneCount * (barHeight + barGap) + 12);
                return (
                  <div
                    key={res.id}
                    className="border-b px-3 flex flex-col justify-center"
                    style={{ height: h }}
                  >
                    <span className="text-xs font-semibold text-foreground truncate">
                      {res.label}
                    </span>
                    {res.subLabel && (
                      <span className="text-[10px] text-muted-foreground truncate">
                        {res.subLabel}
                      </span>
                    )}
                  </div>
                );
              })}
            </div>

            {/* 우측: 타임라인 그리드 */}
            <div className="flex-1 relative" ref={gridRef} style={{ width: totalWidth }}>
              {/* 날짜 헤더 */}
              <div className="sticky top-0 z-10 bg-background border-b">
                {/* 상위 그룹 (월) */}
                {dateGroups && (
                  <div className="flex border-b">
                    {dateGroups.map((g, idx) => (
                      <div
                        key={idx}
                        className="text-center text-[11px] font-semibold text-muted-foreground border-r py-1"
                        style={{ width: g.span * config.cellWidth }}
                      >
                        {g.label}
                      </div>
                    ))}
                  </div>
                )}

                {/* 하위 날짜 셀 */}
                <div className="flex">
                  {dates.map((date, idx) => {
                    const isT = isSameDay(date, today);
                    const isW = isWeekend(date);
                    return (
                      <div
                        key={idx}
                        className={cn(
                          "text-center border-r select-none",
                          isW && "text-red-400",
                          isT && "bg-primary/10 font-bold text-primary"
                        )}
                        style={{
                          width: config.cellWidth,
                          minWidth: config.cellWidth,
                          fontSize: zoom === "month" ? 9 : 11,
                          padding: zoom === "month" ? "2px 0" : "3px 0",
                        }}
                      >
                        {zoom === "month" ? (
                          <div>{date.getDate()}</div>
                        ) : (
                          <>
                            <div className="font-semibold">{date.getDate()}</div>
                            <div>{DAY_NAMES[date.getDay()]}</div>
                          </>
                        )}
                      </div>
                    );
                  })}
                </div>
              </div>

              {/* 리소스별 이벤트 행 */}
              {resources.map((res) => {
                const resEvents = eventsByResource.get(res.id) || [];
                const laneCount = resourceLaneCounts.get(res.id) || 1;
                const h = Math.max(rowHeight, laneCount * (barHeight + barGap) + 12);

                return (
                  <div key={res.id} className="relative border-b" style={{ height: h }}>
                    {/* 배경 그리드 */}
                    <div className="absolute inset-0 flex pointer-events-none">
                      {dates.map((date, idx) => (
                        <div
                          key={idx}
                          className={cn(
                            "border-r border-border/20",
                            isWeekend(date) && "bg-red-500/[0.03]"
                          )}
                          style={{ width: config.cellWidth, minWidth: config.cellWidth }}
                        />
                      ))}
                    </div>

                    {/* 오늘 라인 */}
                    {todayLineLeft != null && (
                      <div
                        className="absolute top-0 bottom-0 w-[2px] bg-red-500 z-[5] pointer-events-none"
                        style={{ left: todayLineLeft }}
                      />
                    )}

                    {/* 이벤트 바 */}
                    {resEvents.map((ev) => {
                      if (ev.isMilestone && showMilestones) {
                        // 마일스톤: 다이아몬드 아이콘
                        const pos = getBarStyle(ev.startDate, ev.startDate);
                        if (!pos) return null;
                        return (
                          <div
                            key={ev.id}
                            className="absolute z-10 flex items-center justify-center cursor-pointer"
                            style={{
                              left: pos.left + pos.width / 2 - 8,
                              top: "50%",
                              transform: "translateY(-50%)",
                            }}
                            title={ev.label || "마일스톤"}
                            onClick={() => onEventClick?.(ev)}
                          >
                            <Diamond className="h-4 w-4 text-purple-500 fill-purple-500" />
                          </div>
                        );
                      }

                      // 일반 이벤트 바
                      const isDragging = dragState?.eventId === ev.id;
                      const barStyle = isDragging
                        ? getDraggedBarStyle(ev)
                        : getBarStyle(ev.startDate, ev.endDate);
                      if (!barStyle) return null;

                      const lane = eventLanes.get(ev.id) || 0;
                      const colorClass = getStatusColor(ev.status);
                      const isConflict = conflictIds.has(ev.id);
                      const progress = ev.progress ?? 0;

                      return (
                        <div
                          key={ev.id}
                          className={cn(
                            "absolute rounded shadow-sm z-10 group select-none",
                            `bg-gradient-to-br ${colorClass}`,
                            isDragging && "opacity-80 shadow-lg z-20",
                            isConflict && "ring-2 ring-red-500 ring-offset-1",
                            "cursor-grab active:cursor-grabbing"
                          )}
                          style={{
                            left: barStyle.left,
                            width: Math.max(barStyle.width, config.cellWidth * 0.5),
                            height: barHeight,
                            top: 6 + lane * (barHeight + barGap),
                          }}
                          title={`${ev.label || ""} | ${ev.startDate.split("T")[0]} ~ ${ev.endDate.split("T")[0]}${progress > 0 ? ` | ${progress}%` : ""}`}
                          onClick={(e) => {
                            if (!isDragging) {
                              e.stopPropagation();
                              onEventClick?.(ev);
                            }
                          }}
                          onMouseDown={(e) => handleMouseDown(e, ev.id, "move", ev.startDate, ev.endDate)}
                        >
                          {/* 진행률 바 */}
                          {showProgress && progress > 0 && (
                            <div
                              className="absolute inset-y-0 left-0 rounded-l bg-white/25"
                              style={{ width: `${Math.min(progress, 100)}%` }}
                            />
                          )}

                          {/* 라벨 */}
                          <span className="absolute inset-0 flex items-center justify-center text-[10px] font-bold text-white drop-shadow-sm truncate px-1">
                            {ev.label || ""}
                            {showProgress && progress > 0 && (
                              <span className="ml-1 opacity-75">({progress}%)</span>
                            )}
                          </span>

                          {/* 좌측 리사이즈 핸들 */}
                          <div
                            className="absolute left-0 top-0 bottom-0 w-[5px] cursor-col-resize opacity-0 group-hover:opacity-100 bg-white/30 rounded-l"
                            onMouseDown={(e) => {
                              e.stopPropagation();
                              handleMouseDown(e, ev.id, "resize-left", ev.startDate, ev.endDate);
                            }}
                          />

                          {/* 우측 리사이즈 핸들 */}
                          <div
                            className="absolute right-0 top-0 bottom-0 w-[5px] cursor-col-resize opacity-0 group-hover:opacity-100 bg-white/30 rounded-r"
                            onMouseDown={(e) => {
                              e.stopPropagation();
                              handleMouseDown(e, ev.id, "resize-right", ev.startDate, ev.endDate);
                            }}
                          />
                        </div>
                      );
                    })}
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
