<script setup lang="ts">
import { Trash2, X } from "lucide-vue-next";
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { createEvent, updateEvent, deleteEvent, type EventEntry, type EventCreatePayload, type EventUpdatePayload } from "@/api/client";
import ProjectSelector from "@/components/ProjectSelector.vue";
import { useToastStore } from "@/stores/toast";

const props = defineProps<{
  // null = create mode; EventEntry = edit mode
  event: EventEntry | null;
  // pre-filled date string for create mode (YYYY-MM-DD or ISO)
  initialDate?: string;
}>();

const emit = defineEmits<{
  (e: "close"): void;
  (e: "created", event: EventEntry): void;
  (e: "updated", event: EventEntry): void;
  (e: "deleted", id: number): void;
}>();

const toast = useToastStore();

const isEditMode = computed(() => !!props.event);
const saving = ref(false);
const deleting = ref(false);
const deleteConfirm = ref(false);

// Form fields
const title = ref("");
const startDate = ref("");
const startTime = ref("");
const endDate = ref("");
const endTime = ref("");
const allDay = ref(false);
const description = ref("");
const location = ref("");
const color = ref("");
const projectId = ref<number | null>(null);
const recurrence = ref<string>("");

// Preset RRULE strings. The select binds to `recurrencePreset`, which writes
// through to `recurrence`. CalDAV-imported rules with extra parts
// (e.g. `FREQ=WEEKLY;BYDAY=MO,WE,FR`) fall through to "custom" and the raw
// string is shown read-only below the select.
const RECURRENCE_PRESETS: Record<string, string> = {
  none: "",
  daily: "FREQ=DAILY",
  weekly: "FREQ=WEEKLY",
  monthly: "FREQ=MONTHLY",
  yearly: "FREQ=YEARLY",
};

const recurrencePreset = computed<string>({
  get() {
    const r = (recurrence.value || "").trim();
    if (!r) return "none";
    for (const [key, val] of Object.entries(RECURRENCE_PRESETS)) {
      if (val && val === r) return key;
    }
    return "custom";
  },
  set(key: string) {
    if (key === "custom") return; // no-op; can't pick custom from dropdown
    recurrence.value = RECURRENCE_PRESETS[key] ?? "";
  },
});

const isCustomRecurrence = computed(() => recurrencePreset.value === "custom");

function dateFromIso(iso: string): string {
  const d = new Date(iso);
  if (isNaN(d.getTime())) return iso.slice(0, 10);
  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}`;
}

function timeFromIso(iso: string): string {
  if (!iso.includes("T")) return "09:00";
  const d = new Date(iso);
  if (isNaN(d.getTime())) return iso.slice(11, 16);
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
}

function toIso(date: string, time: string): string {
  if (!time) return `${date}T00:00:00`;
  // Include local timezone offset so the server stores the correct UTC time
  const local = new Date(`${date}T${time}:00`);
  const off = -local.getTimezoneOffset();
  const sign = off >= 0 ? "+" : "-";
  const h = String(Math.floor(Math.abs(off) / 60)).padStart(2, "0");
  const min = String(Math.abs(off) % 60).padStart(2, "0");
  return `${date}T${time}:00${sign}${h}:${min}`;
}

// ── Time helpers ──────────────────────────────────────────────────────────────

/** Round up to next 30-minute boundary */
function nextRoundedTime(): string {
  const now = new Date();
  let h = now.getHours();
  let m = now.getMinutes();
  if (m <= 30) { m = 30; }
  else { m = 0; h = (h + 1) % 24; }
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}

/** Add hours to a time string (HH:MM), returns HH:MM */
function addHours(time: string, hours: number): string {
  const [h, m] = time.split(":").map(Number);
  const totalMin = h * 60 + m + hours * 60;
  const nh = Math.floor(totalMin / 60) % 24;
  const nm = totalMin % 60;
  return `${String(nh).padStart(2, "0")}:${String(nm).padStart(2, "0")}`;
}

/** Duration in minutes between two time strings */
function durationMin(start: string, end: string): number {
  const [sh, sm] = start.split(":").map(Number);
  const [eh, em] = end.split(":").map(Number);
  return (eh * 60 + em) - (sh * 60 + sm);
}

/** True if a date+time is in the past */
const isPastEvent = computed(() => {
  if (allDay.value || !startDate.value || !startTime.value) return false;
  const dt = new Date(`${startDate.value}T${startTime.value}:00`);
  return dt.getTime() < Date.now();
});

// Track duration so end moves with start
let _lastDurationMin = 60;

function resetForm() {
  if (props.event) {
    title.value = props.event.title;
    allDay.value = props.event.all_day;
    startDate.value = props.event.all_day ? props.event.start_dt.slice(0, 10) : dateFromIso(props.event.start_dt);
    startTime.value = props.event.all_day ? "" : timeFromIso(props.event.start_dt);
    endDate.value = props.event.end_dt ? (props.event.all_day ? props.event.end_dt.slice(0, 10) : dateFromIso(props.event.end_dt)) : startDate.value;
    endTime.value = props.event.end_dt && !props.event.all_day ? timeFromIso(props.event.end_dt) : addHours(startTime.value || "09:00", 1);
    description.value = props.event.description || "";
    location.value = props.event.location || "";
    color.value = props.event.color || "";
    projectId.value = props.event.project_id;
    recurrence.value = props.event.recurrence || "";
    _lastDurationMin = !allDay.value && startTime.value && endTime.value ? durationMin(startTime.value, endTime.value) : 60;
    if (_lastDurationMin <= 0) _lastDurationMin = 60;
  } else {
    title.value = "";
    allDay.value = false;
    const base = props.initialDate ? dateFromIso(props.initialDate) : new Date().toISOString().slice(0, 10);
    const roundedStart = nextRoundedTime();
    startDate.value = base;
    startTime.value = roundedStart;
    endDate.value = base;
    endTime.value = addHours(roundedStart, 1);
    description.value = "";
    location.value = "";
    color.value = "";
    projectId.value = null;
    recurrence.value = "";
    _lastDurationMin = 60;
  }
  deleteConfirm.value = false;
}

// When start time changes, move end time to preserve duration
watch(startTime, (newStart) => {
  if (allDay.value || !newStart) return;
  endTime.value = addHours(newStart, _lastDurationMin / 60);
});

// When start date changes, move end date to match (preserve same-day or multi-day gap)
watch(startDate, (newDate) => {
  if (!newDate) return;
  endDate.value = newDate;
});

// When end time changes manually, update the tracked duration (but guard against end < start)
watch(endTime, (newEnd) => {
  if (allDay.value || !newEnd || !startTime.value) return;
  const dur = durationMin(startTime.value, newEnd);
  if (dur <= 0) {
    // Snap back to start + 1 hour
    endTime.value = addHours(startTime.value, 1);
    _lastDurationMin = 60;
  } else {
    _lastDurationMin = dur;
  }
});

// All-day toggle: clear/restore times
watch(allDay, (isAllDay) => {
  if (isAllDay) {
    startTime.value = "";
    endTime.value = "";
  } else {
    const rounded = nextRoundedTime();
    startTime.value = rounded;
    endTime.value = addHours(rounded, 1);
    _lastDurationMin = 60;
  }
});

watch(() => props.event, resetForm, { immediate: true });
watch(() => props.initialDate, resetForm);

function handleKeydown(e: KeyboardEvent) {
  if (e.key === "Escape") {
    if (deleteConfirm.value) {
      // Esc cancels the delete-confirm rather than closing the modal —
      // gives the user a clear way out of the destructive prompt.
      deleteConfirm.value = false;
      return;
    }
    attemptClose();
  }
}

onMounted(() => document.addEventListener("keydown", handleKeydown));
onUnmounted(() => document.removeEventListener("keydown", handleKeydown));

// ── Close / save flow ─────────────────────────────────────────────────────────
//
// All exit paths (X button, Esc, backdrop click) funnel through `attemptClose`.
// The Save button is gone — explicit-commit is replaced with auto-save-on-close.
//
// Validity-aware behavior:
//   - Form valid → save (PATCH for edit, POST for create), then close.
//   - Form invalid in EDIT mode → discard the in-memory change and close.
//     A toast tells the user what happened so they don't think their edit
//     silently landed.
//   - Form invalid in CREATE mode → close silently (nothing existed to begin
//     with; no need to call this out).

function isFormValid(): { valid: boolean; reason?: string } {
  if (!title.value.trim()) {
    return { valid: false, reason: "Title required" };
  }
  if (!startDate.value) {
    return { valid: false, reason: "Start date required" };
  }
  if (!allDay.value && !startTime.value) {
    return { valid: false, reason: "Start time required" };
  }
  return { valid: true };
}

let _closing = false;

async function attemptClose() {
  if (_closing) return;
  _closing = true;
  try {
    const validity = isFormValid();
    if (!validity.valid) {
      if (isEditMode.value) {
        toast.show(`${validity.reason} — change discarded`, "warning");
      }
      // Create mode + invalid: silent close. Nothing was committed.
      emit("close");
      return;
    }
    await save();
    emit("close");
  } finally {
    _closing = false;
  }
}

async function save() {
  const start_dt = allDay.value ? `${startDate.value}T00:00:00` : toIso(startDate.value, startTime.value);
  const end_dt = endDate.value
    ? (allDay.value ? `${endDate.value}T00:00:00` : toIso(endDate.value, endTime.value))
    : undefined;

  saving.value = true;
  try {
    if (isEditMode.value && props.event) {
      const payload: EventUpdatePayload = {
        title: title.value.trim(),
        start_dt,
        end_dt,
        all_day: allDay.value,
        description: description.value,
        location: location.value,
        color: color.value,
        project_id: projectId.value ?? undefined,
        recurrence: recurrence.value || null,
      };
      const updated = await updateEvent(props.event.id, payload);
      emit("updated", updated);
    } else {
      const payload: EventCreatePayload = {
        title: title.value.trim(),
        start_dt,
        end_dt,
        all_day: allDay.value,
        description: description.value,
        location: location.value,
        color: color.value,
        project_id: projectId.value ?? undefined,
        recurrence: recurrence.value || undefined,
      };
      const created = await createEvent(payload);
      emit("created", created);
    }
  } catch {
    toast.show("Failed to save event", "error");
  } finally {
    saving.value = false;
  }
}

async function doDelete() {
  if (!props.event) return;
  deleting.value = true;
  try {
    await deleteEvent(props.event.id);
    toast.show("Event deleted", "success");
    emit("deleted", props.event.id);
  } catch {
    toast.show("Failed to delete event", "error");
    deleting.value = false;
  }
}
</script>

<template>
  <Teleport to="body">
    <div class="modal-backdrop" @click.self="attemptClose">
      <div class="modal-panel" role="dialog" aria-modal="true">
        <!-- Header: trash + close (or inline delete-confirm) -->
        <div class="modal-header">
          <template v-if="!deleteConfirm">
            <h2 class="modal-title">{{ isEditMode ? "Edit Event" : "New Event" }}</h2>
            <div class="header-actions">
              <button
                v-if="isEditMode"
                class="header-btn header-btn-danger"
                @click="deleteConfirm = true"
                title="Delete event"
                aria-label="Delete event"
              ><Trash2 :size="16" /></button>
              <button
                class="header-btn"
                @click="attemptClose"
                title="Close"
                aria-label="Close"
              ><X :size="16" /></button>
            </div>
          </template>
          <template v-else>
            <span class="delete-confirm-prompt">Delete this event?</span>
            <div class="header-actions">
              <button
                type="button"
                class="btn-danger"
                :disabled="deleting"
                @click="doDelete"
              >{{ deleting ? "Deleting…" : "Yes, delete" }}</button>
              <button
                type="button"
                class="btn-confirm-cancel"
                @click="deleteConfirm = false"
              >No</button>
            </div>
          </template>
        </div>

        <!-- Body: form (scrolls if it gets long) -->
        <form class="modal-form" @submit.prevent="attemptClose">
          <!-- Title -->
          <div class="form-field">
            <label class="form-label">Title <span class="required">*</span></label>
            <input v-model="title" class="form-input" placeholder="Event title" autofocus />
          </div>

          <!-- All-day toggle -->
          <div class="form-field form-field-row">
            <label class="form-label form-label-inline">All day</label>
            <button
              type="button"
              :class="['toggle-btn', { active: allDay }]"
              @click="allDay = !allDay"
            >{{ allDay ? "Yes" : "No" }}</button>
          </div>

          <!-- Start -->
          <div class="form-field">
            <label class="form-label">Start <span class="required">*</span></label>
            <div class="dt-row">
              <input v-model="startDate" type="date" class="form-input dt-date" required />
              <input v-if="!allDay" v-model="startTime" type="time" class="form-input dt-time" required />
            </div>
            <p v-if="isPastEvent" class="form-past-hint">This event is in the past</p>
          </div>

          <!-- End -->
          <div class="form-field">
            <label class="form-label">End</label>
            <div class="dt-row">
              <input v-model="endDate" type="date" class="form-input dt-date" :min="startDate" />
              <input v-if="!allDay" v-model="endTime" type="time" class="form-input dt-time" />
            </div>
          </div>

          <!-- Recurrence -->
          <div class="form-field">
            <label class="form-label">Repeat</label>
            <select v-model="recurrencePreset" class="form-input">
              <option value="none">Does not repeat</option>
              <option value="daily">Daily</option>
              <option value="weekly">Weekly</option>
              <option value="monthly">Monthly</option>
              <option value="yearly">Yearly</option>
              <option v-if="isCustomRecurrence" value="custom" disabled>Custom</option>
            </select>
            <p v-if="isCustomRecurrence" class="recurrence-custom-hint">
              Custom rule: <code>{{ recurrence }}</code>
              <br />
              <span class="form-hint">Picking a preset will replace this rule.</span>
            </p>
          </div>

          <!-- Location -->
          <div class="form-field">
            <label class="form-label">Location <span class="form-hint">(optional)</span></label>
            <input v-model="location" class="form-input" placeholder="Location" />
          </div>

          <!-- Description -->
          <div class="form-field">
            <label class="form-label">Description <span class="form-hint">(optional)</span></label>
            <textarea v-model="description" class="form-input form-textarea" placeholder="Description" rows="3" />
          </div>

          <!-- Color -->
          <div class="form-field form-field-row">
            <label class="form-label form-label-inline">Color</label>
            <div class="color-row">
              <input v-model="color" type="color" class="color-picker" title="Pick event color" />
              <input v-model="color" class="form-input color-hex" placeholder="#5B4A8A" />
              <button v-if="color" type="button" class="btn-clear-color" @click="color = ''"><X :size="16" /></button>
            </div>
          </div>

          <!-- Project -->
          <div class="form-field">
            <label class="form-label">Project <span class="form-hint">(optional)</span></label>
            <ProjectSelector v-model="projectId" />
          </div>

          <!-- A hidden submit so Enter inside text inputs triggers attemptClose,
               matching the no-explicit-Save-button intent: Enter commits. -->
          <button type="submit" class="hidden-submit" :disabled="saving" tabindex="-1" aria-hidden="true" />
        </form>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}

.modal-panel {
  background: var(--color-surface, #1a1b1e);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 12px;
  width: min(480px, 100%);
  max-height: calc(100vh - 2.5rem);
  display: flex;
  flex-direction: column;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.85rem 1rem 0.85rem 1.5rem;
  border-bottom: 1px solid var(--color-border, #2a2b30);
  background: var(--color-surface, #1a1b1e);
  min-height: 3rem;
}

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

.header-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.header-btn {
  background: none;
  border: none;
  color: var(--color-text-muted, #888);
  cursor: pointer;
  padding: 0.4rem;
  border-radius: 6px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
}
.header-btn:hover {
  background: var(--color-hover, rgba(255,255,255,0.06));
  color: var(--color-text, #e8e9f0);
}

/* Trash in header: subtle until hover, then Oxblood. Lower visual weight
   than Save would have been — destructive actions shouldn't loom. */
.header-btn-danger:hover {
  background: var(--color-action-destructive);
  color: #fff;
}

/* Inline delete-confirm prompt replaces the title row */
.delete-confirm-prompt {
  font-size: 0.95rem;
  color: var(--color-text, #e8e9f0);
  font-weight: 500;
}

/* Form scrolls inside the panel when content overflows */
.modal-form {
  padding: 1.25rem 1.5rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  overflow-y: auto;
}

.form-field { display: flex; flex-direction: column; gap: 0.35rem; }
.form-field-row { flex-direction: row; align-items: center; gap: 0.75rem; }

.form-label {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--color-text-muted, #888);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.form-label-inline { flex-shrink: 0; margin: 0; }
.form-hint { font-weight: 400; text-transform: none; letter-spacing: 0; opacity: 0.7; }

.required { color: #f87171; }

.form-input {
  background: var(--color-input-bg, #111113);
  border: 1px solid var(--color-border, #2a2b30);
  color: var(--color-text, #e8e9f0);
  border-radius: 6px;
  padding: 0.5rem 0.65rem;
  font-size: 0.9rem;
  width: 100%;
  box-sizing: border-box;
  transition: border-color 0.15s;
}
.form-input:focus { outline: none; border-color: var(--color-primary); }

.form-textarea { resize: vertical; min-height: 5rem; font-family: inherit; }

.dt-row { display: flex; gap: 0.5rem; }
.dt-date { flex: 1; }
.dt-time { width: 7.5rem; flex-shrink: 0; }
.form-past-hint {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
}

.recurrence-custom-hint {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
  line-height: 1.4;
}
.recurrence-custom-hint code {
  background: var(--color-input-bg, #111113);
  border: 1px solid var(--color-border, #2a2b30);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.72rem;
  color: var(--color-text, #e8e9f0);
}

.toggle-btn {
  background: var(--color-input-bg, #111113);
  border: 1px solid var(--color-border, #2a2b30);
  color: var(--color-text-muted, #888);
  border-radius: 6px;
  padding: 0.3rem 0.9rem;
  font-size: 0.85rem;
  cursor: pointer;
  transition: all 0.15s;
}
.toggle-btn.active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}

.color-row { display: flex; align-items: center; gap: 0.5rem; flex: 1; }
.color-picker { width: 2.4rem; height: 2.2rem; border: none; padding: 0; border-radius: 4px; cursor: pointer; flex-shrink: 0; }
.color-hex { flex: 1; }
.btn-clear-color {
  background: none;
  border: none;
  color: var(--color-text-muted, #888);
  cursor: pointer;
  padding: 0.2rem 0.3rem;
  font-size: 0.85rem;
}

/* Confirm-delete buttons (only shown during the inline confirm flow) */
.btn-danger {
  background: var(--color-action-destructive);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 0.4rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}
.btn-danger:hover:not(:disabled) { background: var(--color-action-destructive-hover); }
.btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-confirm-cancel {
  background: var(--color-action-secondary);
  border: none;
  color: #fff;
  border-radius: 6px;
  padding: 0.4rem 0.85rem;
  font-size: 0.85rem;
  cursor: pointer;
  transition: background 0.15s;
}
.btn-confirm-cancel:hover { background: var(--color-action-secondary-hover); }

/* Hidden submit lets Enter-in-text-input trigger the same close-with-save
   path as X / Esc / backdrop. No visible Save button needed. */
.hidden-submit {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  border: 0;
}
</style>
