<script setup lang="ts">
import { onMounted, computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useNotesStore } from "@/stores/notes";
import { renderMarkdown } from "@/utils/markdown";
import { relativeTime } from "@/composables/useRelativeTime";
import { apiPost, apiGet, apiPatch } from "@/api/client";
import type { Note } from "@/types/note";
import TagPill from "@/components/TagPill.vue";
import TableOfContents from "@/components/TableOfContents.vue";
import ShareDialog from "@/components/ShareDialog.vue";
import { Clock, Pencil, Link as LinkIcon } from "lucide-vue-next";

const route = useRoute();
const router = useRouter();
const store = useNotesStore();
const backlinks = ref<{ type: string; id: number; title: string }[]>([]);
const converting = ref(false);
const showShare = ref(false);

// Context enrichment
const projectTitle = ref<string | null>(null);
const milestoneName = ref<string | null>(null);
const parentNoteTitle = ref<string | null>(null);

const noteId = computed(() => Number(route.params.id));

async function loadContext(note: Note) {
  projectTitle.value = null;
  milestoneName.value = null;
  parentNoteTitle.value = null;

  const promises: Promise<void>[] = [];

  if (note.project_id) {
    promises.push(
      apiGet<any>(`/api/projects/${note.project_id}`).then((data) => {
        projectTitle.value = data.title ?? null;
        if (note.milestone_id && data.summary?.milestone_summary) {
          const ms = (data.summary.milestone_summary as Array<{ id: number; title: string }>)
            .find((m) => m.id === note.milestone_id);
          if (ms) milestoneName.value = ms.title;
        }
      }).catch(() => {})
    );
  }

  if (note.parent_id) {
    promises.push(
      apiGet<Note>(`/api/notes/${note.parent_id}`).then((parent) => {
        parentNoteTitle.value = parent.title || "Untitled";
      }).catch(() => {})
    );
  }

  await Promise.all(promises);
}

async function loadNote(id: number) {
  backlinks.value = [];
  await store.fetchNote(id);

  // If note is empty (no body), redirect to editor
  if (store.currentNote && !store.currentNote.body.trim()) {
    router.replace(`/notes/${id}/edit`);
    return;
  }

  const [bl] = await Promise.allSettled([
    store.fetchBacklinks(id),
    loadContext(store.currentNote!),
  ]);
  if (bl.status === "fulfilled") backlinks.value = bl.value;
}

onMounted(() => loadNote(noteId.value));

// Re-fetch when navigating between notes (Vue reuses the component)
watch(() => route.params.id, (newId) => {
  if (newId) loadNote(Number(newId));
});

const isListNote = computed(() => {
  const body = store.currentNote?.body ?? "";
  return /^- \[[ xX]\] /m.test(body);
});

const renderedBody = computed(() => {
  if (!store.currentNote) return "";
  return renderMarkdown(store.currentNote.body, { interactiveCheckboxes: isListNote.value });
});

async function onBodyChange(e: Event) {
  const target = e.target as HTMLInputElement;
  if (target.type !== "checkbox" || !store.currentNote) return;

  const index = parseInt(target.dataset.taskIndex ?? "", 10);
  if (isNaN(index)) return;

  let taskIdx = 0;
  const newBody = store.currentNote.body.split("\n").map(line => {
    const stripped = line.trimStart();
    if (stripped.startsWith("- [ ] ") || stripped.startsWith("- [x] ") || stripped.startsWith("- [X] ")) {
      if (taskIdx === index) {
        const indent = line.length - stripped.length;
        const wasChecked = !stripped.startsWith("- [ ] ");
        taskIdx++;
        return " ".repeat(indent) + (wasChecked ? "- [ ] " : "- [x] ") + stripped.slice(6);
      }
      taskIdx++;
    }
    return line;
  }).join("\n");

  // Optimistic update so the checkbox state doesn't snap back
  store.currentNote.body = newBody;

  try {
    await apiPatch(`/api/notes/${store.currentNote.id}`, { body: newBody });
  } catch {
    await store.fetchNote(store.currentNote.id);
  }
}

async function onBodyClick(e: MouseEvent) {
  const target = e.target as HTMLElement;

  const tagLink = target.closest(".inline-tag") as HTMLAnchorElement | null;
  if (tagLink) {
    e.preventDefault();
    const tag = tagLink.dataset.tag;
    if (tag) {
      router.push({ path: "/notes", query: { tag } });
    }
    return;
  }

  const wikilink = target.closest(".wikilink") as HTMLAnchorElement | null;
  if (wikilink) {
    e.preventDefault();
    const title = wikilink.dataset.title;
    if (title) {
      try {
        const note = await apiPost<Note>(
          "/api/notes/resolve-title",
          { title }
        );
        router.push(`/notes/${note.id}`);
      } catch {
        const { useToastStore } = await import("@/stores/toast");
        useToastStore().show(`Failed to resolve note "${title}"`, "error");
      }
    }
  }
}

function onTagClick(tag: string) {
  router.push({ path: "/notes", query: { tag } });
}

async function convertToTask() {
  if (converting.value) return;
  converting.value = true;
  try {
    await store.convertToTask(noteId.value);
    const { useToastStore } = await import("@/stores/toast");
    useToastStore().show("Converted to task");
    router.push(`/tasks/${noteId.value}`);
  } catch {
    const { useToastStore } = await import("@/stores/toast");
    useToastStore().show("Failed to convert note", "error");
  } finally {
    converting.value = false;
  }
}
</script>

<template>
  <div class="viewer-layout">
    <main class="viewer">
      <div v-if="store.loading" class="viewer-skeleton" aria-label="Loading note">
        <div class="skel-toolbar">
          <div class="skel-btn"></div>
          <div class="skel-btn skel-btn--wide"></div>
        </div>
        <div class="skel-title"></div>
        <div class="skel-meta"></div>
        <div class="skel-line"></div>
        <div class="skel-line skel-line--short"></div>
        <div class="skel-line"></div>
        <div class="skel-line skel-line--medium"></div>
        <div class="skel-line"></div>
        <div class="skel-line skel-line--short"></div>
      </div>
      <template v-else-if="store.currentNote">
        <div class="toolbar">
          <router-link to="/notes" class="btn-ghost">← Notes</router-link>
          <router-link
            :to="`/notes/${store.currentNote.id}/edit`"
            class="btn-primary"
          >
            Edit
          </router-link>
          <button
            v-if="!store.currentNote.is_task"
            class="btn-secondary btn-compact"
            @click="convertToTask"
            :disabled="converting"
          >
            {{ converting ? "Converting..." : "Convert to Task" }}
          </button>
          <button class="btn-secondary btn-compact" @click="showShare = true">Share</button>
        </div>

        <!-- Breadcrumb: parent → project → milestone -->
        <div
          v-if="store.currentNote.parent_id || store.currentNote.project_id"
          class="context-bar"
        >
          <router-link
            v-if="store.currentNote.parent_id"
            :to="`/notes/${store.currentNote.parent_id}`"
            class="ctx-crumb ctx-crumb-parent"
          >
            ↑ {{ parentNoteTitle || "Parent note" }}
          </router-link>
          <router-link
            v-if="store.currentNote.project_id && projectTitle"
            :to="`/projects/${store.currentNote.project_id}`"
            class="ctx-crumb ctx-crumb-project"
          >
            {{ projectTitle }}
          </router-link>
          <span v-if="milestoneName" class="ctx-crumb ctx-crumb-milestone">
            {{ milestoneName }}
          </span>
        </div>

        <h1 class="note-title">{{ store.currentNote.title || "Untitled" }}</h1>
        <p class="meta">
          <span class="meta-item">
            <Clock :size="16" />
            Updated {{ relativeTime(store.currentNote.updated_at) }}
          </span>
          <span class="meta-sep" aria-hidden="true">·</span>
          <span class="meta-item">
            <Pencil :size="16" />
            Created {{ relativeTime(store.currentNote.created_at) }}
          </span>
        </p>
        <div class="tags" v-if="store.currentNote.tags.length">
          <TagPill
            v-for="tag in store.currentNote.tags"
            :key="tag"
            :tag="tag"
            @click="onTagClick"
          />
        </div>
        <div
          class="body prose"
          :class="{ 'prose--checklist': isListNote }"
          v-html="renderedBody"
          @click="onBodyClick"
          @change="onBodyChange"
        ></div>

        <div v-if="backlinks.length" class="backlinks">
          <h3 class="backlinks-heading">
            <LinkIcon :size="16" />
            Backlinks
            <span class="backlinks-count">{{ backlinks.length }}</span>
          </h3>
          <div class="backlinks-grid">
            <router-link
              v-for="link in backlinks"
              :key="`${link.type}-${link.id}`"
              :to="`/${link.type === 'note' ? 'notes' : 'tasks'}/${link.id}`"
              class="backlink-card"
            >
              <span :class="['backlink-type-badge', `badge-${link.type}`]">{{ link.type }}</span>
              <span class="backlink-title">{{ link.title || "Untitled" }}</span>
            </router-link>
          </div>
        </div>
      </template>
      <p v-else>Note not found.</p>
    </main>
    <TableOfContents
      v-if="store.currentNote?.body"
      :body="store.currentNote.body"
      class="toc-sidebar"
    />
  </div>

  <ShareDialog
    v-if="showShare && store.currentNote"
    resource-type="note"
    :resource-id="store.currentNote.id"
    :resource-title="store.currentNote.title || '(untitled)'"
    @close="showShare = false"
  />
</template>

<style src="@/assets/viewer-shared.css" />
<style scoped>
.viewer-layout {
  display: flex;
  max-width: 1400px;
  margin: 0 auto;
  gap: 2rem;
}
.viewer {
  flex: 1;
  min-width: 0;
  max-width: 1100px;
  margin: 2rem 0;
  padding: 0 1rem;
}
.toc-sidebar {
  margin-top: 2rem;
}
@media (max-width: 1200px) {
  .toc-sidebar {
    display: none;
  }
}
.toolbar {
  display: flex;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
/* Edit: Moss action-primary — switching from view to edit is operating
   the software, not a brand moment. */
/* Convert + Share: Bronze action-secondary — alternate paths */


.meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-size: 0.83rem;
  color: var(--fs-text-tertiary);
  margin: 0 0 0.75rem;
}
.meta-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.meta-sep {
  opacity: 0.5;
}
.tags {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.backlinks {
  margin-top: 2.5rem;
  border-top: 1px solid var(--fs-border-color);
  padding-top: 1.25rem;
}
.backlinks-heading {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fs-text-tertiary);
  margin: 0 0 0.75rem;
}
.backlinks-count {
  margin-left: 0.2rem;
  font-size: 0.72rem;
  background: var(--fs-surface-raised);
  border: 1px solid var(--fs-border-color);
  border-radius: 999px;
  padding: 0 0.4rem;
  line-height: 1.4;
}
.backlinks-grid {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.backlink-card {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--fs-radius-lg);
  background: var(--fs-surface-raised);
  border: 1px solid var(--fs-border-color);
  text-decoration: none;
  color: var(--fs-text-primary);
  transition: border-color 0.15s, box-shadow 0.15s;
  font-size: 0.9rem;
}
.backlink-card:hover {
  border-color: color-mix(in srgb, var(--fs-accent) 50%, transparent);
  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
  color: var(--fs-accent);
}
.backlink-type-badge {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 500;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  flex-shrink: 0;
}
.badge-note {
  background: color-mix(in srgb, var(--fs-accent) 12%, transparent);
  color: var(--fs-accent);
  border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent);
}
.badge-task {
  background: color-mix(in srgb, #f59e0b 12%, transparent);
  color: #d97706;
  border: 1px solid color-mix(in srgb, #f59e0b 30%, transparent);
}
.backlink-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Skeleton loader ── */
@keyframes skel-shine {
  to { background-position: 200% center; }
}
.viewer-skeleton {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  padding-top: 0.5rem;
}
.skel-btn,
.skel-title,
.skel-meta,
.skel-line {
  border-radius: var(--fs-radius-sm);
  background: linear-gradient(
    90deg,
    var(--fs-surface-raised) 25%,
    color-mix(in srgb, var(--fs-text-tertiary) 18%, var(--fs-surface-raised)) 50%,
    var(--fs-surface-raised) 75%
  );
  background-size: 200% 100%;
  animation: skel-shine 1.5s ease infinite;
}
.skel-toolbar {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}
.skel-btn { width: 70px; height: 32px; }
.skel-btn--wide { width: 90px; }
.skel-title { height: 2.2rem; width: 70%; border-radius: var(--fs-radius-lg); }
.skel-meta { height: 0.85rem; width: 40%; }
.skel-line { height: 0.9rem; }
.skel-line--short { width: 55%; }
.skel-line--medium { width: 80%; }
</style>
