<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useNotesStore, type Note, type NoteView } from "../stores/notes";
import { useUiStore } from "../stores/ui";
import { facetCount, facetsFromQuery, facetsToQuery } from "../notes/facets";
import { useNoteEditor } from "../composables/useNoteEditor";
import AsyncState from "../components/AsyncState.vue";
import EmptyState from "../components/EmptyState.vue";
import FilterBar from "../components/FilterBar.vue";
import NoteCard from "../components/NoteCard.vue";
import NoteEditor from "../components/NoteEditor.vue";

const notes = useNotesStore();
const route = useRoute();
const ui = useUiStore();
const router = useRouter();

const composing = ref(false); // compose modal open (a new, empty note)
const loadError = ref("");
// Shared editor controller (open/close/navigate glue lives in the composable).
const { editing, open: openEditor, close: closeEditor, navigate: onNavigate } = useNoteEditor({
  onClose: () => {
    composing.value = false;
  },
});

// Compose and edit are ONE surface: the "Take a note…" bar and the global `c`
// shortcut both open the same modal editor with an empty note (editing = null).
watch(
  () => ui.composeTick,
  () => (composing.value = true),
);

// The command palette opens a note by navigating here with ?open=<id>.
watch(
  () => route.query.open,
  (v) => {
    if (typeof v === "string" && v) void openFromQuery(v);
  },
  { immediate: true },
);
async function openFromQuery(id: string) {
  const found = notes.items.find((n) => n.id === id) ?? (await notes.fetchOne(id));
  if (found) openEditor(found);
  const q = { ...route.query };
  delete q.open;
  void router.replace({ query: q });
}

function viewForRoute(name: unknown): NoteView {
  if (name === "archive") return "archived";
  if (name === "trash") return "trash";
  return "active"; // board + label views
}

const currentView = computed<NoteView>(() => viewForRoute(route.name));
const currentLabel = computed<string | null>(() => (route.name === "label" ? String(route.params.id) : null));

// Facet filters live in the URL query on the board route (a filtered board = a lens).
const facets = computed(() => facetsFromQuery(route.query));
const facetKey = computed(() => JSON.stringify(facetsToQuery(facets.value))); // stable; ignores ?open=
const filtered = computed(() => route.name === "board" && facetCount(facets.value) > 0);

// Quick-add + pinned/others split only on the main board (not archive/trash/label).
const isMainBoard = computed(() => route.name === "board");
const pinnedNotes = computed(() => notes.items.filter((n) => n.pinned));
const otherNotes = computed(() => notes.items.filter((n) => !n.pinned));

// --- Keyboard card navigation: j/k or arrows move a selection; Enter opens;
// e archive, # pin, x trash the focused card. ---
const orderedNotes = computed<Note[]>(() =>
  isMainBoard.value ? [...pinnedNotes.value, ...otherNotes.value] : notes.items,
);
const focusedIndex = ref(-1);
const inTrash = computed(() => currentView.value === "trash");

function moveFocus(delta: number) {
  const count = orderedNotes.value.length;
  if (count === 0) return;
  focusedIndex.value =
    focusedIndex.value < 0 ? 0 : Math.min(Math.max(focusedIndex.value + delta, 0), count - 1);
}

// Two-mode board keyboard. RESTING (no card focused): letters type-to-compose,
// handled globally in AppShell; here only the ARROWS enter browse mode. BROWSING (a
// card focused): j/k move, e/x/# act, Enter opens, Esc exits back to resting.
function onBoardKey(e: KeyboardEvent) {
  if (editing.value || composing.value) return; // a modal owns the keyboard
  const el = e.target as HTMLElement | null;
  if (el && (["INPUT", "TEXTAREA", "BUTTON", "A", "SELECT"].includes(el.tagName) || el.isContentEditable)) return;
  if (e.metaKey || e.ctrlKey || e.altKey) return;
  const key = e.key;
  const browsing = focusedIndex.value >= 0;
  if (key === "ArrowDown" || key === "ArrowRight" || (browsing && key === "j")) {
    e.preventDefault();
    moveFocus(1);
    return;
  }
  if (key === "ArrowUp" || key === "ArrowLeft" || (browsing && key === "k")) {
    e.preventDefault();
    moveFocus(-1);
    return;
  }
  if (!browsing) {
    // Resting: Enter starts a new note; other keys fall to AppShell's type-to-compose.
    if (key === "Enter") {
      e.preventDefault();
      ui.requestCompose();
    }
    return;
  }
  if (key === "Escape") {
    e.preventDefault();
    focusedIndex.value = -1; // leave browse mode, back to type-to-capture
    return;
  }
  const note = orderedNotes.value[focusedIndex.value];
  if (!note) return;
  if (key === "Enter") {
    e.preventDefault();
    openEditor(note);
  } else if (key === "x" && !inTrash.value) {
    e.preventDefault();
    void notes.trash(note.id);
  } else if (key === "e" && !inTrash.value) {
    e.preventDefault();
    void notes.setArchived(note.id, !note.archived);
  } else if (key === "#" && !inTrash.value) {
    e.preventDefault();
    void notes.setPinned(note.id, !note.pinned);
  }
}

// Publish browse-mode state so AppShell's type-to-compose stands down while browsing.
watch(focusedIndex, (i) => (ui.boardCardFocused = i >= 0));

// Keep the selection valid as the list changes / the view switches.
watch(
  () => orderedNotes.value.length,
  (len) => {
    if (focusedIndex.value >= len) focusedIndex.value = len - 1;
  },
);
watch([currentView, currentLabel, facetKey], () => (focusedIndex.value = -1));

const emptyState = computed(() => {
  if (filtered.value) return { title: "No notes match these filters", subtitle: "Try clearing or loosening a facet." };
  if (currentView.value === "trash") return { title: "Trash is empty", subtitle: "Notes you delete land here first." };
  if (currentView.value === "archived")
    return { title: "Nothing archived", subtitle: "Archived notes are tucked away here." };
  if (currentLabel.value) return { title: "No notes with this label", subtitle: "Tag a note to see it here." };
  return { title: "No notes yet", subtitle: "Your captured thoughts will show up here." };
});

async function reload() {
  loadError.value = "";
  try {
    await notes.load(currentView.value, currentLabel.value, facets.value);
  } catch (e) {
    loadError.value = (e as { error?: string }).error ?? "Couldn't load your notes.";
  }
}

onMounted(() => {
  void reload();
  window.addEventListener("keydown", onBoardKey);
});
onBeforeUnmount(() => {
  window.removeEventListener("keydown", onBoardKey);
  ui.boardCardFocused = false;
});
watch([currentView, currentLabel, facetKey], reload);

const draggingId = ref<string | null>(null);
function onDragStart(note: Note) {
  draggingId.value = note.id;
}
function onDragEnd() {
  // Clear the tracked source even when a drag is cancelled (dropped on nothing).
  draggingId.value = null;
}
async function onDrop(target: Note) {
  const from = draggingId.value;
  draggingId.value = null;
  if (!from || from === target.id) return;
  const order = notes.items.map((n) => n.id);
  const fromIdx = order.indexOf(from);
  const toIdx = order.indexOf(target.id);
  if (fromIdx < 0 || toIdx < 0) return;
  order.splice(fromIdx, 1);
  order.splice(toIdx, 0, from);
  await notes.reorder(order);
}
</script>

<template>
  <div class="mx-auto w-full max-w-6xl px-4 py-6">
    <button
      v-if="isMainBoard"
      type="button"
      class="mx-auto mb-4 block w-full max-w-xl rounded-xl border border-dashed border-neutral-300 px-4 py-2.5 text-center text-sm text-neutral-400 transition hover:border-neutral-400 hover:text-neutral-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:hover:border-neutral-500"
      @click="ui.requestCompose()"
    >
      Press
      <kbd class="mx-0.5 rounded border border-neutral-300 px-1 text-xs dark:border-neutral-600">Enter</kbd>
      or start typing to add a note
    </button>
    <FilterBar v-if="isMainBoard" />

    <AsyncState
      :loading="notes.loading"
      :error="loadError || undefined"
      error-title="Couldn't load your notes"
      @retry="reload"
    >
      <EmptyState v-if="notes.items.length === 0" :title="emptyState.title" :subtitle="emptyState.subtitle" />
      <template v-else>
      <template v-if="isMainBoard">
        <section v-if="pinnedNotes.length">
          <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Pinned</h2>
          <div class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
            <NoteCard
              v-for="(n, i) in pinnedNotes"
              :key="n.id"
              :note="n"
              :active="focusedIndex === i"
              reorderable
              @open="openEditor"
              @dragstart="onDragStart"
              @dragend="onDragEnd"
              @drop="onDrop"
            />
          </div>
        </section>
        <section v-if="otherNotes.length" :class="pinnedNotes.length ? 'mt-8' : ''">
          <h2 v-if="pinnedNotes.length" class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">
            Others
          </h2>
          <div class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
            <NoteCard
              v-for="(n, i) in otherNotes"
              :key="n.id"
              :note="n"
              :active="focusedIndex === pinnedNotes.length + i"
              reorderable
              @open="openEditor"
              @dragstart="onDragStart"
              @dragend="onDragEnd"
              @drop="onDrop"
            />
          </div>
        </section>
      </template>

      <div v-else class="columns-1 gap-4 sm:columns-2 lg:columns-3 xl:columns-4">
        <NoteCard
          v-for="(n, i) in notes.items"
          :key="n.id"
          :note="n"
          :active="focusedIndex === i"
          reorderable
          @open="openEditor"
          @dragstart="onDragStart"
          @dragend="onDragEnd"
          @drop="onDrop"
        />
      </div>
      </template>
    </AsyncState>
  </div>

  <template v-if="composing || editing">
    <NoteEditor
      :note="editing"
      :initial-body="composing ? ui.composeSeed : ''"
      @close="closeEditor"
      @navigate="onNavigate"
    />
  </template>
</template>
