// frontend/components/calendar/CalendarClient.tsx — 일정 페이지 오케스트레이터 (실제 날짜 기반)
"use client";
import { useEffect, useRef, useState, type MouseEvent } from "react";
import { useParams } from "next/navigation";
import { Icon } from "@/components/Icon";
import { useDialog } from "@/components/Dialog";
import { calendarApi } from "@/lib/calendar/api";
import { loadLS, saveLS } from "@/lib/calendar/store";
import {
  addDays,
  addMonths,
  dayNum,
  monthNum,
  weekDatesOf,
  yearNum,
} from "@/lib/calendar/time";
import type { Calendar, CalEvent, EventWrite, WeekBundle } from "@/lib/calendar/types";
import { MiniCal } from "./MiniCal";
import { CalToggleList } from "./CalToggleList";
import { WeekView } from "./WeekView";
import { MonthView } from "./MonthView";
import { DayView } from "./DayView";
import { EvPopover } from "./EvPopover";
import { EvEditor } from "./EvEditor";
import { MeetDrawer } from "./MeetDrawer";

const VIEWS = [
  { id: "week", label: "주" },
  { id: "month", label: "월" },
  { id: "day", label: "일" },
];

function countByDate(events: CalEvent[]): Record<string, number> {
  const m: Record<string, number> = {};
  events.forEach((e) => {
    m[e.date] = (m[e.date] || 0) + 1;
  });
  return m;
}

function CalendarSkeleton() {
  return (
    <div className="calpage">
      <div className="twork" aria-busy="true">
        <aside className="subnav">
          <div className="sn-title">일정</div>
          <div className="cal-skel-col">
            {[0, 1, 2, 3].map((i) => (
              <div key={i} className="cal-skel" style={{ height: 34 }} />
            ))}
          </div>
        </aside>
        <main className="cmain">
          <div className="cal-skel" style={{ height: 40, width: 240, marginBottom: 16 }} />
          <div className="cpanel">
            <div className="cal-skel" style={{ height: 420 }} />
          </div>
        </main>
      </div>
    </div>
  );
}

export function CalendarClient() {
  const { confirm, alert } = useDialog();
  const [bundle, setBundle] = useState<WeekBundle | null>(null);
  const [err, setErr] = useState(false);
  const [view, setView] = useState<string>("week");
  const [refDate, setRefDate] = useState<string>(""); // 네비게이션 기준일(ISO)
  const [selDate, setSelDate] = useState<string>(""); // 일 뷰 선택일(ISO)
  const [calOn, setCalOn] = useState<Record<string, boolean>>({});
  const [focusOn, setFocusOn] = useState<boolean>(true);
  const [hydrated, setHydrated] = useState(false);
  const [pop, setPop] = useState<{ ev: CalEvent; x: number; y: number } | null>(null);
  const [meetEv, setMeetEv] = useState<CalEvent | null>(null);
  const [editor, setEditor] = useState<{
    ev: CalEvent | null;
    date?: string;
    time?: string;
  } | null>(null);
  const shellRef = useRef<HTMLDivElement>(null);
  const didMount = useRef(false);

  // URL 로 열린 일정을 표현 — /calendar/<id> 의 <id> 세그먼트(옵셔널 캐치올).
  // 주소 갱신은 router 대신 history.replaceState(얕은 갱신) — router.replace 는 이 라우트를
  // 리마운트시켜 주간 네비게이션·상태가 날아가기 때문.
  const params = useParams();
  const routeId = Array.isArray(params.slug) ? params.slug[0] : undefined;

  // 클라이언트에서만 localStorage 복원 (SSR 안정). 날짜는 항상 오늘에서 시작(stale 방지).
  useEffect(() => {
    setView(loadLS("view", "week"));
    setCalOn(loadLS<Record<string, boolean>>("calOn", {}));
    setFocusOn(loadLS("focusOn", true));
    setHydrated(true);
  }, []);

  const load = () => {
    setErr(false);
    calendarApi
      .week()
      .then((b) => {
        setBundle(b);
        setCalOn((cur) =>
          Object.keys(cur).length ? cur : Object.fromEntries(b.calendars.map((c) => [c.id, c.on])),
        );
        setRefDate((cur) => cur || b.today_date);
        setSelDate((cur) => cur || b.today_date);
      })
      .catch(() => setErr(true));
  };
  useEffect(load, []);

  // 백엔드 자동 풀링이 적재한 새 일정을 주기적으로(10초)·포커스 시 반영(네비게이션 상태는 유지).
  useEffect(() => {
    const refetch = () => {
      calendarApi
        .week()
        .then((b) => setBundle(b))
        .catch(() => {});
    };
    const id = setInterval(refetch, 10_000);
    window.addEventListener("focus", refetch);
    return () => {
      clearInterval(id);
      window.removeEventListener("focus", refetch);
    };
  }, []);

  useEffect(() => {
    if (hydrated) saveLS("view", view);
  }, [view, hydrated]);
  useEffect(() => {
    if (hydrated) saveLS("calOn", calOn);
  }, [calOn, hydrated]);
  useEffect(() => {
    if (hydrated) saveLS("focusOn", focusOn);
  }, [focusOn, hydrated]);

  useEffect(() => {
    const el = shellRef.current;
    const id = requestAnimationFrame(() => el && el.classList.add("entered"));
    return () => cancelAnimationFrame(id);
  }, [bundle]);

  // URL → 일정: routeId 가 가리키는 일정이 있으면 그 주로 이동 + 팝오버 오픈(직접 입력·뒤로가기·딥링크).
  useEffect(() => {
    if (!bundle) return; // 번들 로드 후에만 탐색 가능
    if (!routeId) {
      setPop((p) => (p ? null : p)); // 주소에서 빠지면 팝오버 닫기
      return;
    }
    if (pop?.ev.id === routeId) return; // 이미 열려 있음
    const ev = bundle.events.find((e) => e.id === routeId);
    if (!ev) return; // 없는 id → 무시(잘못된 주소)
    setRefDate(ev.date);
    setSelDate(ev.date);
    setPop({ ev, x: Math.max(window.innerWidth / 2 - 150, 16), y: 140 });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [routeId, bundle]);

  // 일정 → URL: 팝오버로 연 일정에 주소를 맞춘다(초기 마운트는 위 effect 가 처리하므로 skip).
  useEffect(() => {
    if (!didMount.current) {
      didMount.current = true;
      return;
    }
    const id = pop?.ev.id;
    const target = id ? `/calendar/${id}` : "/calendar";
    if (window.location.pathname !== target) window.history.replaceState(null, "", target);
  }, [pop]);

  if (err && !bundle) {
    return (
      <div className="calpage">
        <div className="cal-error" role="alert">
          <Icon name="x" /> 일정을 불러오지 못했어요
          <button onClick={load}>다시 시도</button>
        </div>
      </div>
    );
  }

  if (!bundle) return <CalendarSkeleton />;

  const today = bundle.today_date;
  const ref = refDate || today;
  const sel = selDate || today;
  const weekDates = weekDatesOf(ref);

  const calMap: Record<string, Calendar> = Object.fromEntries(
    bundle.calendars.map((c) => [c.id, c]),
  );
  // 안전망: 이벤트가 참조하는 캘린더가 목록에 없어도 렌더가 깨지지 않도록 기본값 보강.
  for (const e of bundle.events) {
    if (!calMap[e.cal]) {
      calMap[e.cal] = { id: e.cal, name: e.cal || "기타", tone: "ink", on: true, count: 0 };
    }
  }
  const events = bundle.events.filter((e) => calOn[e.cal] !== false);
  const focusBlocks = focusOn ? bundle.focus_blocks : [];
  const openEv = (ev: CalEvent, e: MouseEvent) =>
    setPop({ ev, x: e.clientX + 8, y: e.clientY + 8 });
  const openMeet = (ev: CalEvent) => {
    setPop(null);
    setMeetEv(ev);
  };
  const afterWrite = () => {
    setEditor(null);
    setPop(null);
    load();
  };
  const deleteEvent = async (ev: CalEvent) => {
    setPop(null);
    const ok = await confirm({
      title: "일정 삭제",
      message: `‘${ev.title}’ 일정을 삭제할까요?`,
      confirmText: "삭제",
      tone: "danger",
    });
    if (!ok) return;
    try {
      await calendarApi.deleteEvent(ev.id);
      load();
    } catch (e) {
      alert({ title: "삭제 실패", message: e instanceof Error ? e.message : "삭제하지 못했어요" });
    }
  };
  // 빈 시간 슬롯 클릭 → 해당 날짜·시각으로 생성 모달 오픈.
  const openSlot = (date: string, time: string) =>
    setEditor({ ev: null, date, time });
  // 팝오버 "내 작업에 추가" → 회의 액션 전체를 작업으로 변환.
  const addToTasks = async (ev: CalEvent) => {
    await calendarApi.materializeAll(ev.id);
  };
  // 드래그 이동/리사이즈 → 날짜·시각 변경을 서버에 반영(낙관적 갱신 후 재로드).
  const evToWrite = (ev: CalEvent, patch: Partial<EventWrite>): EventWrite => ({
    title: ev.title,
    date: ev.date,
    start: ev.start,
    end: ev.end,
    loc: ev.loc,
    note: ev.note,
    people: ev.people,
    cal: ev.cal,
    rrule: ev.rrule,
    reminders: ev.reminders,
    ...patch,
  });
  const moveEvent = (ev: CalEvent, patch: { date: string; start: string; end: string }) => {
    setBundle(
      (b) =>
        b && {
          ...b,
          events: b.events.map((e) =>
            e.id === ev.id
              ? { ...e, ...patch, day: Number(patch.date.slice(8, 10)) || e.day }
              : e,
          ),
        },
    );
    calendarApi
      .updateEvent(ev.id, evToWrite(ev, patch))
      .then(load)
      .catch((e) => {
        alert({
          title: "이동 실패",
          message: e instanceof Error ? e.message : "일정을 옮기지 못했어요",
        });
        load();
      });
  };
  // 초대 응답(RSVP) → 서버 반영 + 팝오버 즉시 갱신.
  const rsvp = async (ev: CalEvent, status: "accepted" | "declined" | "tentative") => {
    try {
      const updated = await calendarApi.rsvp(ev.id, status);
      setPop((p) => (p && p.ev.id === ev.id ? { ...p, ev: updated } : p));
      load();
    } catch (e) {
      alert({
        title: "응답 실패",
        message: e instanceof Error ? e.message : "응답을 보내지 못했어요",
      });
    }
  };

  const nav = (dir: number) => {
    if (view === "month") setRefDate(addMonths(ref, dir));
    else if (view === "day") {
      const nd = addDays(sel, dir);
      setSelDate(nd);
      setRefDate(nd);
    } else setRefDate(addDays(ref, dir * 7));
  };
  const goToday = () => {
    setRefDate(today);
    setSelDate(today);
  };
  const pickDay = (iso: string) => {
    setSelDate(iso);
    setRefDate(iso);
    setView("day");
  };

  const ws = weekDates[0];
  const we = weekDates[6];
  const title =
    view === "month"
      ? `${yearNum(ref)}년 ${monthNum(ref)}월`
      : view === "day"
        ? `${monthNum(sel)}월 ${dayNum(sel)}일`
        : monthNum(ws) === monthNum(we)
          ? `${monthNum(ws)}월 ${dayNum(ws)}일 – ${dayNum(we)}일`
          : `${monthNum(ws)}월 ${dayNum(ws)}일 – ${monthNum(we)}월 ${dayNum(we)}일`;

  return (
    <div className="calpage" ref={shellRef}>
      <div className="twork">
        <aside className="subnav">
          <div className="sn-title">일정</div>
          <button className="sn-new" onClick={() => setEditor({ ev: null })}>
            <Icon name="plus" /> 새 일정
          </button>
          <MiniCal
            monthRef={ref}
            sel={sel}
            today={today}
            weekdays={bundle.weekdays}
            eventsByDate={countByDate(events)}
            onSel={pickDay}
            onMonth={(dir) => setRefDate(addMonths(ref, dir))}
          />
          <div className="sn-label">내 캘린더</div>
          <CalToggleList
            calendars={bundle.calendars}
            calOn={calOn}
            onToggle={(id) => setCalOn((s) => ({ ...s, [id]: s[id] === false }))}
          />
          <div className="sn-foot">
            <div className="av">지</div>
            <div className="txt">
              <b>지우님</b>
              <span>Pro 플랜</span>
            </div>
          </div>
        </aside>

        <main className="cmain">
          <div className="chead">
            <button className="ch-arrow" aria-label="이전" onClick={() => nav(-1)}>
              <Icon name="chev" className="ic flip" />
            </button>
            <button className="ch-arrow" aria-label="다음" onClick={() => nav(1)}>
              <Icon name="chev" />
            </button>
            <h1 className="ch-title">{title}</h1>
            <button className="ch-today" onClick={goToday}>
              오늘
            </button>
            <span className="ch-spacer" />
            <button
              className={"ch-focus" + (focusOn ? " on" : "")}
              onClick={() => setFocusOn((v) => !v)}
              aria-pressed={focusOn}
            >
              <Icon name="spark" />
              집중 블록
            </button>
            <div className="view-seg" role="tablist">
              {VIEWS.map((v) => (
                <button
                  key={v.id}
                  role="tab"
                  aria-selected={view === v.id}
                  className={view === v.id ? "on" : ""}
                  onClick={() => setView(v.id)}
                >
                  {v.label}
                </button>
              ))}
            </div>
          </div>

          {view === "week" && (
            <WeekView
              events={events}
              focusBlocks={focusBlocks}
              calMap={calMap}
              weekDates={weekDates}
              weekdays={bundle.weekdays}
              todayDate={today}
              onOpen={openEv}
              onSlotClick={openSlot}
              onCommit={moveEvent}
            />
          )}
          {view === "month" && (
            <MonthView
              events={events}
              calMap={calMap}
              monthRef={ref}
              weekdays={bundle.weekdays}
              todayDate={today}
              onOpen={openEv}
              onSelDay={pickDay}
            />
          )}
          {view === "day" && (
            <DayView
              events={events}
              focusBlocks={focusBlocks}
              calMap={calMap}
              dayDate={sel}
              todayDate={today}
              onOpen={openEv}
              onMeet={openMeet}
              onSlotClick={openSlot}
              onCommit={moveEvent}
            />
          )}
        </main>
      </div>

      {pop && (
        <EvPopover
          ev={pop.ev}
          x={pop.x}
          y={pop.y}
          cal={calMap[pop.ev.cal]}
          onClose={() => setPop(null)}
          onMeet={openMeet}
          onEdit={(ev) => {
            setPop(null);
            setEditor({ ev });
          }}
          onDelete={deleteEvent}
          onAddToTasks={addToTasks}
          onRsvp={rsvp}
        />
      )}
      {meetEv && (
        <MeetDrawer ev={meetEv} cal={calMap[meetEv.cal]} onClose={() => setMeetEv(null)} />
      )}
      {editor && (
        <EvEditor
          calendars={bundle.calendars}
          initial={editor.ev}
          defaultDate={editor.date || sel}
          defaultTime={editor.time}
          onClose={() => setEditor(null)}
          onSaved={afterWrite}
          onDeleted={afterWrite}
        />
      )}
    </div>
  );
}
