<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { RouterLink } from "vue-router";
import { apiGet, apiPatch, apiPost, apiDelete } from "@/api/client";
import { useToastStore } from "@/stores/toast";
import TaskLogSection from "@/components/TaskLogSection.vue";
import { renderMarkdown } from "@/utils/markdown";
import { Trash2, X } from "lucide-vue-next";
import { relativeTimeOrDate } from "@/composables/useRelativeTime";

const props = defineProps<{ projectId: number }>();

const toast = useToastStore();

interface Milestone {
  id: number;
  title: string;
  status: string;
  order_index: number;
}

interface Task {
  id: number;
  title: string;
  status: string;
  priority: string;
  milestone_id: number | null;
  due_date: string | null;
  updated_at: string;
  body?: string;
}

const tasks = ref<Task[]>([]);
const milestones = ref<Milestone[]>([]);
const loading = ref(false);

// Detail slide-over
const activeTask = ref<Task | null>(null);
const taskBody = ref("");
const loadingBody = ref(false);

// Collapsed milestone groups (set of milestone IDs, null = "No Milestone" group)
const collapsedGroups = ref<Set<number | null>>(new Set());

// New task quick-add
const newTaskTitle = ref("");
const addingTask = ref(false);

const STATUS_CYCLE: Record<string, string> = {
  todo: "in_progress",
  in_progress: "done",
  done: "todo",
};

const STATUS_ICON: Record<string, string> = {
  todo: "○",
  in_progress: "▶",
  done: "✓",
};

const PRIORITY_CLASS: Record<string, string> = {
  high: "priority-high",
  medium: "priority-medium",
  low: "priority-low",
};

function isRowOverdue(task: Task): boolean {
  if (!task.due_date || task.status === "done") return false;
  return task.due_date < new Date().toISOString().slice(0, 10);
}

// Group tasks by milestone, "No Milestone" first
const groupedTasks = computed(() => {
  const noMs = tasks.value.filter((t) => t.milestone_id === null);
  const msGroups = milestones.value.map((ms) => ({
    milestone: ms,
    tasks: tasks.value.filter((t) => t.milestone_id === ms.id),
  }));
  return { noMilestone: noMs, milestoneGroups: msGroups };
});

async function loadAll() {
  loading.value = true;
  try {
    const [tasksRes, msRes] = await Promise.all([
      apiGet<{ notes: Task[] }>(`/api/projects/${props.projectId}/notes?type=task&limit=200`),
      apiGet<{ milestones: Milestone[] }>(`/api/projects/${props.projectId}/milestones`),
    ]);
    tasks.value = tasksRes.notes ?? [];
    milestones.value = msRes.milestones ?? [];
  } catch {
    toast.show("Failed to load tasks", "error");
  } finally {
    loading.value = false;
  }
}

async function cycleStatus(task: Task, e: Event) {
  e.stopPropagation();
  const next = STATUS_CYCLE[task.status] ?? "todo";
  try {
    await apiPatch(`/api/tasks/${task.id}/status`, { status: next });
    task.status = next;
    if (activeTask.value?.id === task.id) activeTask.value.status = next;
  } catch {
    toast.show("Failed to update status", "error");
  }
}

function toggleGroup(key: number | null) {
  if (collapsedGroups.value.has(key)) {
    collapsedGroups.value.delete(key);
  } else {
    collapsedGroups.value.add(key);
  }
}

async function openTask(task: Task) {
  activeTask.value = task;
  taskBody.value = task.body ?? "";
  loadingBody.value = true;
  try {
    const full = await apiGet<Task>(`/api/tasks/${task.id}`);
    taskBody.value = full.body ?? "";
    // Update cached task so re-opens don't re-fetch if unchanged
    task.body = full.body;
  } catch {
    // Non-critical — body just won't show
  } finally {
    loadingBody.value = false;
  }
}

function closeTask() {
  activeTask.value = null;
  taskBody.value = "";
  deleteConfirmPending.value = false;
}

async function addTask() {
  const title = newTaskTitle.value.trim();
  if (!title) return;
  addingTask.value = true;
  try {
    const task = await apiPost<Task>("/api/tasks", {
      title,
      project_id: props.projectId,
      status: "todo",
    });
    tasks.value.unshift(task);
    newTaskTitle.value = "";
  } catch {
    toast.show("Failed to create task", "error");
  } finally {
    addingTask.value = false;
  }
}

const deletingTask = ref(false);
const deleteConfirmPending = ref(false);
const changingMilestone = ref(false);

async function setMilestone(milestoneId: number | null) {
  if (!activeTask.value) return;
  changingMilestone.value = true;
  try {
    await apiPatch(`/api/notes/${activeTask.value.id}`, { milestone_id: milestoneId });
    activeTask.value.milestone_id = milestoneId;
    // Update in tasks list too
    const t = tasks.value.find((x) => x.id === activeTask.value!.id);
    if (t) t.milestone_id = milestoneId;
  } catch {
    toast.show("Failed to update milestone", "error");
  } finally {
    changingMilestone.value = false;
  }
}

async function deleteActiveTask() {
  if (!activeTask.value) return;
  if (!deleteConfirmPending.value) {
    deleteConfirmPending.value = true;
    return;
  }
  deletingTask.value = true;
  try {
    await apiDelete(`/api/tasks/${activeTask.value.id}`);
    tasks.value = tasks.value.filter((t) => t.id !== activeTask.value!.id);
    activeTask.value = null;
    deleteConfirmPending.value = false;
  } catch {
    toast.show("Failed to delete task", "error");
  } finally {
    deletingTask.value = false;
  }
}

function cancelDeleteTask() {
  deleteConfirmPending.value = false;
}

onMounted(loadAll);
defineExpose({ reload: loadAll });
</script>

<template>
  <div class="ws-task-panel">

    <!-- Task list — always visible, shrinks when detail is open -->
    <div :class="['task-list-view', { 'has-detail': !!activeTask }]">
      <div class="panel-header">
        <span class="panel-title">Tasks</span>
      </div>

      <div class="task-add">
        <input
          v-model="newTaskTitle"
          class="task-add-input"
          placeholder="New task..."
          @keydown.enter="addTask"
        />
        <button class="btn-primary btn-inline btn-add" :disabled="addingTask || !newTaskTitle.trim()" @click="addTask">+</button>
      </div>

      <div v-if="loading" class="state-msg">Loading...</div>

      <div v-else class="groups-scroll">
        <!-- No Milestone group (always first) -->
        <div class="ms-group">
          <button class="ms-group-header" @click="toggleGroup(null)">
            <span class="ms-chevron">{{ collapsedGroups.has(null) ? '▶' : '▼' }}</span>
            <span class="ms-name">No Milestone</span>
            <span class="ms-count">{{ groupedTasks.noMilestone.length }}</span>
          </button>
          <ul v-show="!collapsedGroups.has(null)" class="task-items">
            <li
              v-for="task in groupedTasks.noMilestone"
              :key="task.id"
              :class="['task-row', { 'task-active': activeTask?.id === task.id }]"
              @click="openTask(task)"
            >
              <button :class="['status-dot', `status-${task.status}`]" :title="`${task.status} — click to cycle`" @click="cycleStatus(task, $event)">{{ STATUS_ICON[task.status] ?? '○' }}</button>
              <span v-if="task.priority && task.priority !== 'none'" :class="['priority-dot', PRIORITY_CLASS[task.priority] ?? '']"></span>
              <span class="task-title" :class="{ done: task.status === 'done' }">{{ task.title }}</span>
              <span v-if="task.due_date" :class="['task-due', { overdue: isRowOverdue(task) }]">{{ task.due_date }}</span>
              <span class="task-age">{{ relativeTimeOrDate(task.updated_at) }}</span>
            </li>
            <li v-if="groupedTasks.noMilestone.length === 0" class="empty-group">No tasks</li>
          </ul>
        </div>

        <!-- Milestone groups -->
        <div v-for="{ milestone, tasks: msTasks } in groupedTasks.milestoneGroups" :key="milestone.id" class="ms-group">
          <button class="ms-group-header" @click="toggleGroup(milestone.id)">
            <span class="ms-chevron">{{ collapsedGroups.has(milestone.id) ? '▶' : '▼' }}</span>
            <span class="ms-name">{{ milestone.title }}</span>
            <span :class="['ms-status', `ms-status-${milestone.status}`]">{{ milestone.status.replace('_',' ') }}</span>
            <span class="ms-count">{{ msTasks.length }}</span>
          </button>
          <ul v-show="!collapsedGroups.has(milestone.id)" class="task-items">
            <li
              v-for="task in msTasks"
              :key="task.id"
              :class="['task-row', { 'task-active': activeTask?.id === task.id }]"
              @click="openTask(task)"
            >
              <button :class="['status-dot', `status-${task.status}`]" :title="`${task.status} — click to cycle`" @click="cycleStatus(task, $event)">{{ STATUS_ICON[task.status] ?? '○' }}</button>
              <span v-if="task.priority && task.priority !== 'none'" :class="['priority-dot', PRIORITY_CLASS[task.priority] ?? '']"></span>
              <span class="task-title" :class="{ done: task.status === 'done' }">{{ task.title }}</span>
              <span v-if="task.due_date" :class="['task-due', { overdue: isRowOverdue(task) }]">{{ task.due_date }}</span>
              <span class="task-age">{{ relativeTimeOrDate(task.updated_at) }}</span>
            </li>
            <li v-if="msTasks.length === 0" class="empty-group">No tasks</li>
          </ul>
        </div>
      </div>
    </div>

    <!-- Detail pane — bottom split when a task is selected -->
    <Transition name="detail-fade">
      <div v-if="activeTask" class="task-detail">
        <div class="detail-header">
          <RouterLink :to="`/tasks/${activeTask.id}/edit`" target="_blank" class="btn-text btn-edit-task" title="Open full editor">Edit ↗</RouterLink>
          <span :class="['status-badge', `status-${activeTask.status}`]" @click="cycleStatus(activeTask, $event)" title="Click to cycle status">
            {{ STATUS_ICON[activeTask.status] ?? "○" }} {{ activeTask.status.replace("_", " ") }}
          </span>
          <template v-if="deleteConfirmPending">
            <button class="btn-danger-outline btn-inline btn-delete-confirm" :disabled="deletingTask" @click="deleteActiveTask">{{ deletingTask ? '...' : 'Delete?' }}</button>
            <button class="btn-text" aria-label="Cancel delete" @click="cancelDeleteTask"><X :size="16" /></button>
          </template>
          <button v-else class="btn-text btn-delete-task" title="Delete task" @click="deleteActiveTask">
            <Trash2 :size="16" />
          </button>
          <button class="btn-text btn-close-detail" @click="closeTask" aria-label="Close detail"><X :size="16" /></button>
        </div>

        <h3 class="detail-title">{{ activeTask.title }}</h3>

        <div class="detail-meta">
          <span v-if="activeTask.priority && activeTask.priority !== 'none'" class="meta-chip priority">{{ activeTask.priority }}</span>
          <span v-if="activeTask.due_date" class="meta-chip due">Due {{ activeTask.due_date }}</span>
          <select
            class="milestone-select"
            :value="activeTask.milestone_id ?? ''"
            :disabled="changingMilestone"
            @change="setMilestone(($event.target as HTMLSelectElement).value === '' ? null : Number(($event.target as HTMLSelectElement).value))"
          >
            <option value="">No Milestone</option>
            <option v-for="ms in milestones" :key="ms.id" :value="ms.id">{{ ms.title }}</option>
          </select>
        </div>

        <div v-if="taskBody || loadingBody" class="detail-body">
          <div v-if="loadingBody" class="body-loading">…</div>
          <div v-else class="prose" v-html="renderMarkdown(taskBody)" />
        </div>

        <div class="detail-log">
          <TaskLogSection :task-id="activeTask.id" />
        </div>
      </div>
    </Transition>

  </div>
</template>

<style scoped>
.ws-task-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--fs-surface-hover);
  border-right: 1px solid var(--fs-border-color);
}

/* ── List view ── */
.task-list-view {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.task-list-view.has-detail {
  flex: 0 0 44%;
}
.task-active {
  background: color-mix(in srgb, var(--fs-accent) 6%, var(--fs-surface-hover)) !important;
}

.panel-header {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--fs-border-color);
  flex-shrink: 0;
}

.panel-title {
  color: var(--fs-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.75rem;
  font-weight: 500;
}

.task-add {
  display: flex;
  gap: 0.4rem;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--fs-border-color);
  flex-shrink: 0;
}

.task-add-input {
  flex: 1;
  background: var(--fs-surface-page);
  border: 1px solid var(--fs-border-color);
  border-radius: 5px;
  padding: 0.28rem 0.5rem;
  font-size: 0.83rem;
  color: var(--fs-text-primary);
}
.task-add-input:focus { outline: none; border-color: var(--fs-accent); }

.btn-add { font-size: 1rem; }   /* a '+' glyph, not a label */

.groups-scroll {
  flex: 1;
  overflow-y: auto;
}

.ms-group {
  border-bottom: 1px solid var(--fs-border-color);
}

.ms-group-header {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.4rem 0.65rem;
  background: var(--fs-surface-raised);
  border: none;
  cursor: pointer;
  text-align: left;
  font-size: 0.8rem;
  color: var(--fs-text-primary);
}
.ms-group-header:hover { background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-hover)); }

.ms-chevron { font-size: 0.6rem; color: var(--fs-text-tertiary); width: 0.8rem; }
.ms-name { flex: 1; font-weight: 500; font-size: 0.8rem; }
.ms-count { font-size: 0.72rem; color: var(--fs-text-tertiary); background: var(--fs-surface-page); border-radius: 10px; padding: 0 0.4rem; }

.ms-status {
  font-size: 0.68rem;
  padding: 0.1rem 0.4rem;
  border-radius: 10px;
  text-transform: capitalize;
}
.ms-status-active { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent); }
.ms-status-completed { background: color-mix(in srgb, var(--fs-success) 15%, transparent); color: var(--fs-success); }

.task-items {
  list-style: none;
  margin: 0;
  padding: 0;
}

.task-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.65rem 0.35rem 1.4rem;
  cursor: pointer;
  border-bottom: 1px solid color-mix(in srgb, var(--fs-border-color) 50%, transparent);
}
.task-row:hover { background: color-mix(in srgb, var(--fs-accent) 5%, var(--fs-surface-hover)); }
.task-row:last-child { border-bottom: none; }

.status-dot {
  flex-shrink: 0;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  border: 1.5px solid var(--fs-border-color);
  background: none;
  cursor: pointer;
  font-size: 0.62rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.status-dot.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent); }
.status-dot.status-done { border-color: var(--fs-success); color: var(--fs-success); }

.task-title {
  flex: 1;
  font-size: 0.83rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fs-text-primary);
}
.task-title.done { text-decoration: line-through; color: var(--fs-text-tertiary); }

.task-age {
  font-size: 0.68rem;
  color: var(--fs-text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}

.empty-group { padding: 0.4rem 1.4rem; font-size: 0.78rem; color: var(--fs-text-tertiary); }

.state-msg { padding: 1.5rem; text-align: center; font-size: 0.85rem; color: var(--fs-text-tertiary); }

/* ── Detail pane (bottom split) ── */
.task-detail {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--fs-surface-hover);
  border-top: 2px solid var(--fs-border-color);
}

.detail-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--fs-border-color);
  flex-shrink: 0;
}

.status-badge {
  padding: 0.2rem 0.55rem;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 500;
  cursor: pointer;
  border: 1.5px solid var(--fs-border-color);
  background: none;
  text-transform: capitalize;
  user-select: none;
  margin-left: auto;
}
.status-badge.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); }
.status-badge.status-done { border-color: var(--fs-success); color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 10%, transparent); }

.btn-edit-task { margin-left: 0.25rem; }
.btn-edit-task:hover { text-decoration: underline; }

.detail-body {
  padding: 0.5rem 0.75rem 0.5rem;
  border-bottom: 1px solid var(--fs-border-color);
  flex-shrink: 0;
  max-height: 40%;
  overflow-y: auto;
}

.body-loading {
  font-size: 0.8rem;
  color: var(--fs-text-tertiary);
}

.detail-body .prose {
  font-size: 0.83rem;
  line-height: 1.5;
  color: var(--fs-text-primary);
}

.btn-delete-task { margin-left: 0.25rem; }
.btn-delete-task:hover { color: var(--fs-action-destructive); }

.btn-delete-confirm { margin-left: 0.25rem; }


.detail-meta {
  display: flex;
  gap: 0.4rem;
  padding: 0 0.75rem 0.5rem;
  flex-shrink: 0;
}

.meta-chip {
  font-size: 0.72rem;
  padding: 0.15rem 0.5rem;
  border-radius: 10px;
  background: var(--fs-surface-page);
  border: 1px solid var(--fs-border-color);
  color: var(--fs-text-tertiary);
  text-transform: capitalize;
}

.milestone-select {
  font-size: 0.72rem;
  padding: 0.15rem 0.4rem;
  border-radius: 10px;
  background: var(--fs-surface-page);
  border: 1px solid var(--fs-border-color);
  color: var(--fs-text-tertiary);
  cursor: pointer;
  max-width: 140px;
}
.milestone-select:disabled { opacity: 0.5; cursor: default; }
.milestone-select:focus { outline: none; border-color: var(--fs-accent); }

.detail-log {
  flex: 1;
  overflow-y: auto;
  padding: 0 0.6rem 0.6rem;
  border-top: 1px solid var(--fs-border-color);
}

/* Detail fade transition */
.detail-fade-enter-active,
.detail-fade-leave-active { transition: opacity 0.15s; }
.detail-fade-enter-from,
.detail-fade-leave-to { opacity: 0; }

/* Priority dots on task rows */
.priority-dot {
  width: 6px;
  height: 6px;
  border-radius: 2px;
  flex-shrink: 0;
}
.priority-high   { background: #ef4444; }
.priority-medium { background: #f59e0b; }
.priority-low    { background: #3b82f6; }

/* Due date on task rows */
.task-due {
  font-size: 0.65rem;
  color: var(--fs-text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}
.task-due.overdue {
  color: var(--fs-error);
  font-weight: 500;
}

/* Close detail button */
.btn-close-detail { margin-left: 0.2rem; }
</style>
