<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from "vue";
import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import type { CalendarOptions, EventClickArg, EventDropArg } from "@fullcalendar/core";
import type { DateClickArg, EventResizeDoneArg } from "@fullcalendar/interaction";
import { listEvents, updateEvent, type EventEntry } from "@/api/client";
import EventSlideOver from "@/components/EventSlideOver.vue";
import { useToastStore } from "@/stores/toast";
import { fmtTime, fmtDateTime, fmtDayLabel } from "@/utils/dateFormat";
import { MapPin } from "lucide-vue-next";

const toast = useToastStore();
const calendarRef = ref<InstanceType<typeof FullCalendar> | null>(null);

// Slide-over state
const slideOverEvent = ref<EventEntry | null>(null);
const slideOverOpen = ref(false);
const slideOverDate = ref<string>("");

function openCreate(date: string) {
  slideOverEvent.value = null;
  slideOverDate.value = date;
  slideOverOpen.value = true;
}

function openEdit(event: EventEntry) {
  slideOverEvent.value = event;
  slideOverDate.value = "";
  slideOverOpen.value = true;
}

function closeSlideOver() {
  slideOverOpen.value = false;
}

// Event entry cache keyed by id
const eventCache = new Map<number, EventEntry>();

function toFcEvent(e: EventEntry) {
  // For all-day events pass date-only strings so FullCalendar never shifts
  // the date through timezone conversion (UTC midnight → previous day in UTC-X).
  return {
    id: String(e.id),
    title: e.title,
    start: e.all_day ? e.start_dt.slice(0, 10) : e.start_dt,
    end: e.all_day ? (e.end_dt?.slice(0, 10) ?? undefined) : (e.end_dt ?? undefined),
    allDay: e.all_day,
    backgroundColor: e.color || undefined,
    borderColor: e.color || undefined,
    extendedProps: { entryId: e.id },
  };
}

// ── Upcoming events list ───────────────────────────────────────────────────
const upcomingEvents = ref<EventEntry[]>([]);

async function loadUpcoming() {
  const now = new Date();
  const end = new Date(now.getTime() + 28 * 86_400_000); // 4 weeks
  try {
    const entries = await listEvents(now.toISOString(), end.toISOString());
    upcomingEvents.value = entries.sort(
      (a, b) => new Date(a.start_dt).getTime() - new Date(b.start_dt).getTime()
    );
  } catch { /* silent */ }
}

onMounted(loadUpcoming);

// ── Month/year picker ──────────────────────────────────────────────────────
const currentViewYear = ref(new Date().getFullYear());
const currentViewMonth = ref(new Date().getMonth());
const pickerOpen = ref(false);
const pickerYear = ref(new Date().getFullYear());
const pickerStyle = ref<Record<string, string>>({});
const pickerEl = ref<HTMLElement | null>(null);

const MONTH_NAMES = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] as const;

function handleDatesSet(arg: { view: { currentStart: Date } }) {
  const d = arg.view.currentStart;
  currentViewYear.value = d.getFullYear();
  currentViewMonth.value = d.getMonth();
}

function jumpTo(year: number, month: number) {
  calendarRef.value?.getApi().gotoDate(new Date(year, month, 1));
  pickerOpen.value = false;
}

// ── Event popover ──────────────────────────────────────────────────────────
const popover = ref<EventEntry | null>(null);
const popoverStyle = ref<Record<string, string>>({});
const popoverEl = ref<HTMLElement | null>(null);

function showPopover(entry: EventEntry, clickEvent: MouseEvent) {
  popover.value = entry;
  nextTickPositionPopover(clickEvent);
}

function nextTickPositionPopover(clickEvent: MouseEvent) {
  // Position after DOM update
  requestAnimationFrame(() => {
    const vw = window.innerWidth;
    const vh = window.innerHeight;
    const pw = 280;
    const ph = 220; // approximate
    let left = clickEvent.clientX + 8;
    let top = clickEvent.clientY + 8;
    if (left + pw > vw - 16) left = clickEvent.clientX - pw - 8;
    if (top + ph > vh - 16) top = clickEvent.clientY - ph - 8;
    popoverStyle.value = {
      position: "fixed",
      left: `${Math.max(8, left)}px`,
      top: `${Math.max(8, top)}px`,
      zIndex: "9999",
    };
  });
}

function closePopover() {
  popover.value = null;
}

function onPopoverEdit() {
  if (popover.value) {
    openEdit(popover.value);
    closePopover();
  }
}

// Close popover / open picker on outside or title click
function onDocClick(e: MouseEvent) {
  const target = e.target as HTMLElement;
  // Title click → toggle month/year picker
  const titleEl = target.closest(".fc-toolbar-title");
  if (titleEl) {
    if (!pickerOpen.value) {
      pickerYear.value = currentViewYear.value;
      const rect = titleEl.getBoundingClientRect();
      const left = Math.max(8, Math.min(rect.left + rect.width / 2 - 140, window.innerWidth - 296));
      pickerStyle.value = {
        position: "fixed",
        top: `${rect.bottom + 6}px`,
        left: `${left}px`,
        zIndex: "9999",
      };
    }
    pickerOpen.value = !pickerOpen.value;
    return;
  }
  // Close picker on outside click
  if (pickerOpen.value && pickerEl.value && !pickerEl.value.contains(target)) {
    pickerOpen.value = false;
  }
  // Close event popover on outside click
  if (popover.value && popoverEl.value && !popoverEl.value.contains(target)) {
    closePopover();
  }
}

function onCalendarChanged() {
  calendarRef.value?.getApi().refetchEvents();
}

onMounted(() => {
  document.addEventListener("mousedown", onDocClick);
  document.addEventListener("fable:calendar-changed", onCalendarChanged);
});
onUnmounted(() => {
  document.removeEventListener("mousedown", onDocClick);
  document.removeEventListener("fable:calendar-changed", onCalendarChanged);
});

// ── Calendar callbacks ─────────────────────────────────────────────────────
async function loadEvents(
  fetchInfo: { startStr: string; endStr: string },
  successCallback: (events: object[]) => void,
  failureCallback: (error: Error) => void,
) {
  try {
    const entries = await listEvents(fetchInfo.startStr, fetchInfo.endStr);
    eventCache.clear();
    for (const e of entries) eventCache.set(e.id, e);
    successCallback(entries.map(toFcEvent));
    loadUpcoming();
  } catch (err) {
    failureCallback(err instanceof Error ? err : new Error(String(err)));
  }
}

function handleDateClick(arg: DateClickArg) {
  closePopover();
  openCreate(arg.dateStr);
}

function handleEventClick(arg: EventClickArg) {
  const id = arg.event.extendedProps.entryId as number;
  const entry = eventCache.get(id);
  if (entry) showPopover(entry, arg.jsEvent as MouseEvent);
}

async function handleEventDrop(arg: EventDropArg) {
  const id = arg.event.extendedProps.entryId as number;
  const start_dt = arg.event.startStr;
  const end_dt = arg.event.endStr || undefined;
  try {
    const updated = await updateEvent(id, { start_dt, end_dt, all_day: arg.event.allDay });
    eventCache.set(id, updated);
    loadUpcoming();
  } catch {
    arg.revert();
    toast.show("Failed to move event", "error");
  }
}

async function handleEventResize(arg: EventResizeDoneArg) {
  const id = arg.event.extendedProps.entryId as number;
  const start_dt = arg.event.startStr;
  const end_dt = arg.event.endStr || undefined;
  try {
    const updated = await updateEvent(id, { start_dt, end_dt });
    eventCache.set(id, updated);
    loadUpcoming();
  } catch {
    arg.revert();
    toast.show("Failed to resize event", "error");
  }
}

function onCreated(entry: EventEntry) {
  eventCache.set(entry.id, entry);
  calendarRef.value?.getApi().addEvent(toFcEvent(entry));
  closeSlideOver();
  loadUpcoming();
}

function onUpdated(entry: EventEntry) {
  eventCache.set(entry.id, entry);
  const api = calendarRef.value?.getApi();
  if (api) {
    const existing = api.getEventById(String(entry.id));
    if (existing) {
      existing.remove();
      api.addEvent(toFcEvent(entry));
    }
  }
  closeSlideOver();
  loadUpcoming();
}

function onDeleted(id: number) {
  eventCache.delete(id);
  const api = calendarRef.value?.getApi();
  if (api) api.getEventById(String(id))?.remove();
  closeSlideOver();
  upcomingEvents.value = upcomingEvents.value.filter((e) => e.id !== id);
}

const calendarOptions: CalendarOptions = {
  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
  initialView: "dayGridMonth",
  timeZone: "local",
  editable: true,
  selectable: false,
  headerToolbar: {
    left: "prev,next today",
    center: "title",
    right: "dayGridMonth,timeGridWeek,timeGridDay",
  },
  events: loadEvents,
  datesSet: handleDatesSet,
  dateClick: handleDateClick,
  eventClick: handleEventClick,
  eventDrop: handleEventDrop,
  eventResize: handleEventResize,
  height: "auto",
};

// Group upcoming events by day label
const upcomingGrouped = computed(() => {
  const groups: { label: string; date: string; events: EventEntry[] }[] = [];
  for (const e of upcomingEvents.value) {
    const label = fmtDayLabel(e.start_dt);
    const existing = groups.find((g) => g.label === label);
    if (existing) {
      existing.events.push(e);
    } else {
      groups.push({ label, date: e.start_dt, events: [e] });
    }
  }
  return groups;
});
</script>

<template>
  <div class="calendar-view">
    <div class="cal-header">
      <h1 class="cal-title">Calendar</h1>
      <button class="btn-new-event" @click="openCreate(new Date().toISOString().slice(0, 10))">
        + New Event
      </button>
    </div>

    <div class="fc-wrapper">
      <FullCalendar ref="calendarRef" :options="calendarOptions" />
    </div>

    <!-- ── Upcoming events strip ───────────────────────────────────────── -->
    <div v-if="upcomingEvents.length" class="upcoming-section">
      <h2 class="upcoming-title">Upcoming</h2>
      <div class="upcoming-groups">
        <div v-for="group in upcomingGrouped" :key="group.label" class="upcoming-group">
          <div class="upcoming-day-label">{{ group.label }}</div>
          <div class="upcoming-cards">
            <div
              v-for="ev in group.events"
              :key="ev.id"
              class="upcoming-card"
              :style="ev.color ? { '--ev-color': ev.color } : {}"
              @click="openEdit(ev)"
            >
              <div class="upcoming-card-accent"></div>
              <div class="upcoming-card-body">
                <div class="upcoming-card-title">{{ ev.title }}</div>
                <div class="upcoming-card-time">
                  <template v-if="ev.all_day">All day</template>
                  <template v-else>
                    {{ fmtTime(ev.start_dt) }}
                    <span v-if="ev.end_dt"> – {{ fmtTime(ev.end_dt) }}</span>
                  </template>
                </div>
                <div v-if="ev.location" class="upcoming-card-meta">
                  <MapPin :size="16" style="flex-shrink:0" />
                  {{ ev.location }}
                </div>
                <div v-if="ev.description" class="upcoming-card-desc">{{ ev.description }}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- ── Event popover ───────────────────────────────────────────────── -->
    <Teleport to="body">
      <div
        v-if="popover"
        ref="popoverEl"
        class="event-popover"
        :style="popoverStyle"
      >
        <div class="popover-accent" :style="popover.color ? { background: popover.color } : {}"></div>
        <div class="popover-content">
          <div class="popover-title">{{ popover.title }}</div>
          <div class="popover-time">
            <template v-if="popover.all_day">
              {{ fmtDateTime(popover.start_dt, true) }}
            </template>
            <template v-else>
              {{ fmtDateTime(popover.start_dt, false) }}
              <span v-if="popover.end_dt"> – {{ fmtTime(popover.end_dt) }}</span>
            </template>
          </div>
          <div v-if="popover.location" class="popover-meta">
            <MapPin :size="16" style="flex-shrink:0;margin-top:1px" />
            {{ popover.location }}
          </div>
          <div v-if="popover.description" class="popover-desc">{{ popover.description }}</div>
          <div class="popover-actions">
            <button class="popover-btn popover-btn--edit" @click="onPopoverEdit">Edit</button>
            <button class="popover-btn popover-btn--close" @click="closePopover">Close</button>
          </div>
        </div>
      </div>
    </Teleport>

    <!-- ── Month/year picker ──────────────────────────────────────────── -->
    <Teleport to="body">
      <div v-if="pickerOpen" ref="pickerEl" class="month-picker" :style="pickerStyle">
        <div class="picker-year-row">
          <button class="picker-year-btn" @click="pickerYear--" aria-label="Previous year">‹</button>
          <span class="picker-year-label">{{ pickerYear }}</span>
          <button class="picker-year-btn" @click="pickerYear++" aria-label="Next year">›</button>
        </div>
        <div class="picker-months">
          <button
            v-for="(name, i) in MONTH_NAMES"
            :key="i"
            class="picker-month"
            :class="{ active: pickerYear === currentViewYear && i === currentViewMonth }"
            @click="jumpTo(pickerYear, i)"
          >{{ name }}</button>
        </div>
      </div>
    </Teleport>

    <EventSlideOver
      v-if="slideOverOpen"
      :event="slideOverEvent"
      :initial-date="slideOverDate"
      @close="closeSlideOver"
      @created="onCreated"
      @updated="onUpdated"
      @deleted="onDeleted"
    />
  </div>
</template>

<style scoped>
.calendar-view {
  max-width: var(--page-max-width);
  margin: 0 auto;
  padding: 1.5rem 1.5rem 3rem;
}

.cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}

.cal-title {
  font-size: 1.5rem;
  font-weight: 500;
  margin: 0;
  color: var(--color-text, #e8e9f0);
}

/* New event: Moss action-primary — workflow action, not a brand moment */
.btn-new-event {
  background: var(--color-action-primary);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.55rem 1.1rem;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}
.btn-new-event:hover { background: var(--color-action-primary-hover); }

.fc-wrapper {
  background: var(--color-surface);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: var(--radius-lg, 18px);
  padding: 1rem;
  overflow: hidden;
}

/* FullCalendar dark theme overrides */
:deep(.fc) {
  color: var(--color-text, #e8e9f0);
  font-family: inherit;
}
:deep(.fc-toolbar-title) {
  /* FullCalendar renders this as <h2>, which would otherwise pick up the
     theme.css h1/h2 → Fraunces rule. At 1.1rem it's below the doc's
     "Fraunces only at ≥18px" threshold, so explicit Inter. */
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 1.1rem;
  font-weight: 500;
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 8px;
  transition: background 0.15s;
  user-select: none;
}
:deep(.fc-toolbar-title:hover) {
  background: rgba(255,255,255,0.07);
}
:deep(.fc-button) {
  background: var(--color-input-bg, var(--color-bg));
  border: 1px solid var(--color-border, #2a2b30);
  color: var(--color-text-muted, #888);
  font-size: 0.82rem;
  padding: 0.3rem 0.7rem;
  box-shadow: none;
}
:deep(.fc-button:hover),
:deep(.fc-button-active) {
  background: var(--color-primary) !important;
  border-color: var(--color-primary) !important;
  color: #fff !important;
}
:deep(.fc-button:focus) { box-shadow: none !important; }
:deep(.fc-daygrid-day-number),
:deep(.fc-col-header-cell-cushion) {
  color: var(--color-text-muted, #888);
  text-decoration: none;
  font-size: 0.82rem;
}
:deep(.fc-daygrid-day.fc-day-today) {
  background: var(--color-primary-tint);
}
:deep(.fc-event) {
  border-radius: 4px;
  border: none;
  padding: 1px 4px;
  font-size: 0.78rem;
  cursor: pointer;
}
:deep(.fc-event-main) { color: #fff; }
:deep(.fc-event:not([style*="background"])) {
  background: var(--color-primary);
}
:deep(.fc-daygrid-day-frame) { min-height: 5rem; }
:deep(.fc-scrollgrid) { border-color: var(--color-border, #2a2b30); }
:deep(.fc-scrollgrid td),
:deep(.fc-scrollgrid th) { border-color: var(--color-border, #2a2b30); }
:deep(.fc-daygrid-day) { cursor: pointer; }
:deep(.fc-daygrid-day:hover) { background: rgba(255,255,255,0.03); }

/* ── Month/year picker ──────────────────────────────────────────────────── */
.month-picker {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  width: 280px;
  padding: 12px 14px;
}

.picker-year-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.picker-year-label {
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--color-text, #e8e9f0);
}

.picker-year-btn {
  background: none;
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 6px;
  color: var(--color-text-muted, #888);
  cursor: pointer;
  padding: 2px 10px;
  font-size: 1rem;
  line-height: 1.4;
  transition: color 0.15s, border-color 0.15s;
}
.picker-year-btn:hover {
  color: var(--color-text, #e8e9f0);
  border-color: rgba(255,255,255,0.25);
}

.picker-months {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}

.picker-month {
  padding: 7px 4px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--color-text, #e8e9f0);
  cursor: pointer;
  font-size: 0.84rem;
  text-align: center;
  transition: background 0.12s, color 0.12s;
}
.picker-month:hover {
  background: rgba(255,255,255,0.08);
}
.picker-month.active {
  background: rgba(91, 74, 138,0.22);
  color: var(--color-primary);
  font-weight: 500;
}

/* ── Upcoming strip ─────────────────────────────────────────────────────── */
.upcoming-section {
  margin-top: 2rem;
}

.upcoming-title {
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-text-muted, #888);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.78rem;
  margin: 0 0 1rem;
}

.upcoming-groups {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.upcoming-day-label {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--color-text-muted, #888);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.5rem;
}

.upcoming-cards {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.upcoming-card {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.upcoming-card:hover {
  border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
  background: var(--color-bg-secondary);
}

.upcoming-card-accent {
  width: 4px;
  flex-shrink: 0;
  background: var(--ev-color, #5B4A8A);
}

.upcoming-card-body {
  padding: 0.6rem 0.85rem;
  flex: 1;
  min-width: 0;
}

.upcoming-card-title {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--color-text, #e8e9f0);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.upcoming-card-time {
  font-size: 0.78rem;
  color: var(--color-text-muted, #888);
  margin-top: 0.15rem;
}

.upcoming-card-meta {
  display: flex;
  align-items: flex-start;
  gap: 0.3rem;
  font-size: 0.78rem;
  color: var(--color-text-muted, #888);
  margin-top: 0.25rem;
}

.upcoming-card-desc {
  font-size: 0.8rem;
  color: var(--color-text-secondary, #aaa);
  margin-top: 0.3rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Event popover ──────────────────────────────────────────────────────── */
.event-popover {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.45);
  width: 280px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.popover-accent {
  height: 4px;
  background: var(--color-primary);
}

.popover-content {
  padding: 0.85rem 1rem 0.75rem;
}

.popover-title {
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--color-text, #e8e9f0);
  margin-bottom: 0.35rem;
}

.popover-time {
  font-size: 0.8rem;
  color: var(--color-text-muted, #888);
  margin-bottom: 0.4rem;
}

.popover-meta {
  display: flex;
  align-items: flex-start;
  gap: 0.3rem;
  font-size: 0.8rem;
  color: var(--color-text-muted, #888);
  margin-bottom: 0.4rem;
}

.popover-desc {
  font-size: 0.82rem;
  color: var(--color-text-secondary, #aaa);
  line-height: 1.45;
  margin-bottom: 0.6rem;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.popover-actions {
  display: flex;
  gap: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--color-border, #2a2b30);
}

.popover-btn {
  flex: 1;
  padding: 0.35rem 0;
  border: none;
  border-radius: 6px;
  font-size: 0.82rem;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s;
}
.popover-btn:hover { opacity: 0.85; }
.popover-btn--edit {
  background: var(--color-primary);
  color: #fff;
}
.popover-btn--close {
  background: var(--color-input-bg, var(--color-bg));
  color: var(--color-text-muted, #888);
  border: 1px solid var(--color-border, #2a2b30);
}
</style>
