<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from "vue";
import { api } from "../api/client";
import { useNotesStore } from "../stores/notes";
import { useTitlesStore } from "../stores/titles";
import ColorPicker from "./ColorPicker.vue";
import Icon from "./Icon.vue";
import LabelPicker from "./LabelPicker.vue";
import NoteChecklist from "./NoteChecklist.vue";
import type { Note, NoteLabel } from "../stores/notes";
import type { NoteColor } from "../notes/colors";

const props = defineProps<{ note: Note }>();
const emit = defineEmits<{ (e: "close"): void; (e: "navigate", id: string): void }>();
const notes = useNotesStore();
const titles = useTitlesStore();

// Read the note reactively from the store so checklist item add/toggle/delete
// (which reconcile a fresh note object) reflect live while the editor is open.
const liveNote = computed(() => notes.items.find((n) => n.id === props.note.id) ?? props.note);

const title = ref(props.note.title ?? "");
const body = ref(props.note.body);
const color = ref<NoteColor>(props.note.color);
const labelList = ref<NoteLabel[]>([...props.note.labels]);
const bodyInput = ref<HTMLTextAreaElement | null>(null);

watch(
  () => props.note,
  (n) => {
    title.value = n.title ?? "";
    body.value = n.body;
    color.value = n.color;
    labelList.value = [...n.labels];
  },
);

const backlinks = ref<{ id: string; title: string }[]>([]);

async function loadBacklinks() {
  try {
    const res = await api.get<{ backlinks: { id: string; title: string }[] }>(
      `/api/notes/${props.note.id}/backlinks`,
    );
    backlinks.value = res.backlinks;
  } catch {
    backlinks.value = [];
  }
}

onMounted(async () => {
  void titles.load();
  void loadBacklinks();
  await nextTick();
  bodyInput.value?.focus();
});

watch(
  () => props.note.id,
  () => void loadBacklinks(),
);

const outgoingLinks = computed(() => {
  const re = /\[\[([^[\]]+)\]\]/g;
  const seen = new Set<string>();
  const out: { title: string; id: string | null }[] = [];
  let match: RegExpExecArray | null;
  while ((match = re.exec(body.value)) !== null) {
    const title = match[1].trim();
    const key = title.toLowerCase();
    if (title && !seen.has(key)) {
      seen.add(key);
      out.push({ title, id: titles.resolve(title)?.id ?? null });
    }
  }
  return out;
});

async function openLink(link: { title: string; id: string | null }) {
  if (link.id) {
    emit("navigate", link.id);
    return;
  }
  const created = await notes.createTitled(link.title);
  await titles.reload();
  emit("navigate", created.id);
}

async function onLabelsChange(next: NoteLabel[]) {
  labelList.value = next;
  await notes.setLabels(
    props.note.id,
    next.map((lb) => lb.id),
  );
}

async function removeLabel(id: string) {
  await onLabelsChange(labelList.value.filter((lb) => lb.id !== id));
}

async function toggleKind() {
  if (liveNote.value.kind === "list") {
    await notes.setKind(props.note.id, "text");
    return;
  }
  // Convert existing body lines into checklist items, then switch to a list.
  const lines = body.value
    .split("\n")
    .map((s) => s.trim())
    .filter((s) => s.length > 0);
  for (const line of lines) await notes.addItem(props.note.id, line);
  if (lines.length > 0) {
    body.value = "";
    await notes.saveEdit(props.note.id, { title: title.value, body: "", color: color.value });
  }
  await notes.setKind(props.note.id, "list");
}

const fileInput = ref<HTMLInputElement | null>(null);
const uploadError = ref("");

function pickImage() {
  fileInput.value?.click();
}

async function uploadFile(file: File) {
  uploadError.value = "";
  try {
    await notes.uploadAttachment(props.note.id, file);
  } catch (e) {
    uploadError.value = (e as { error?: string }).error ?? "Could not upload image.";
  }
}

async function onFileChange(e: Event) {
  const input = e.target as HTMLInputElement;
  const file = input.files?.[0];
  if (file) await uploadFile(file);
  input.value = "";
}

async function onPaste(e: ClipboardEvent) {
  const item = Array.from(e.clipboardData?.items ?? []).find((i) => i.type.startsWith("image/"));
  const file = item?.getAsFile();
  if (file) {
    e.preventDefault();
    await uploadFile(file);
  }
}

async function close() {
  const changed =
    (title.value.trim() || null) !== (props.note.title ?? null) ||
    body.value !== props.note.body ||
    color.value !== props.note.color;
  if (changed) {
    await notes.saveEdit(props.note.id, { title: title.value, body: body.value, color: color.value });
  }
  emit("close");
}

async function act(fn: () => Promise<void>) {
  await fn();
  emit("close");
}
</script>

<template>
  <div
    class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 p-4 pt-[10vh]"
    @mousedown.self="close"
  >
    <div
      class="w-full max-w-lg rounded-xl border border-neutral-200 bg-white shadow-xl dark:border-neutral-700 dark:bg-neutral-900"
      role="dialog"
      aria-modal="true"
      @keydown.esc="close"
      @paste="onPaste"
    >
      <div class="flex flex-col gap-2 p-4">
        <div v-if="liveNote.attachments.length" class="flex flex-wrap gap-2">
          <div v-for="att in liveNote.attachments" :key="att.id" class="group/att relative">
            <img :src="att.url" alt="" class="h-24 w-24 rounded-lg object-cover" />
            <button
              type="button"
              class="absolute right-1 top-1 rounded-full bg-black/50 px-1.5 text-white opacity-0 transition group-hover/att:opacity-100"
              aria-label="Remove image"
              @click="notes.deleteAttachment(note.id, att.id)"
            >
              ×
            </button>
          </div>
        </div>
        <p v-if="uploadError" class="text-xs text-red-600 dark:text-red-400">{{ uploadError }}</p>
        <input
          v-model="title"
          type="text"
          placeholder="Title"
          class="w-full bg-transparent text-base font-semibold outline-none placeholder:text-neutral-400"
        />
        <textarea
          v-if="liveNote.kind === 'text'"
          ref="bodyInput"
          v-model="body"
          rows="8"
          placeholder="Take a note…"
          class="w-full resize-none bg-transparent text-sm leading-relaxed outline-none placeholder:text-neutral-400"
        />
        <NoteChecklist v-else class="py-1" :note-id="liveNote.id" :items="liveNote.items" editable />

        <div v-if="labelList.length" class="flex flex-wrap gap-1.5 pt-1">
          <span
            v-for="lb in labelList"
            :key="lb.id"
            class="inline-flex items-center gap-1 rounded-full bg-black/5 px-2 py-0.5 text-xs text-neutral-600 dark:bg-white/10 dark:text-neutral-300"
          >
            {{ lb.name }}
            <button
              type="button"
              class="text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100"
              :aria-label="`Remove ${lb.name}`"
              @click="removeLabel(lb.id)"
            >
              ×
            </button>
          </span>
        </div>

        <div
          v-if="outgoingLinks.length || backlinks.length"
          class="flex flex-col gap-2 border-t border-neutral-100 pt-2 dark:border-neutral-800"
        >
          <div v-if="outgoingLinks.length">
            <p class="mb-1 text-xs font-semibold uppercase tracking-wide text-neutral-400">Links</p>
            <div class="flex flex-wrap gap-1.5">
              <button
                v-for="link in outgoingLinks"
                :key="link.title"
                type="button"
                class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs"
                :class="
                  link.id
                    ? 'bg-brand/15 text-brand-700 dark:text-brand'
                    : 'bg-black/5 text-neutral-500 dark:bg-white/10 dark:text-neutral-400'
                "
                :title="link.id ? `Open ${link.title}` : `Create ${link.title}`"
                @click="openLink(link)"
              >
                {{ link.title }}<span v-if="!link.id" class="opacity-60">＋</span>
              </button>
            </div>
          </div>
          <div v-if="backlinks.length">
            <p class="mb-1 text-xs font-semibold uppercase tracking-wide text-neutral-400">Linked from</p>
            <div class="flex flex-wrap gap-1.5">
              <button
                v-for="b in backlinks"
                :key="b.id"
                type="button"
                class="inline-flex items-center rounded-full bg-black/5 px-2 py-0.5 text-xs text-neutral-600 hover:bg-black/10 dark:bg-white/10 dark:text-neutral-300"
                @click="emit('navigate', b.id)"
              >
                {{ b.title }}
              </button>
            </div>
          </div>
        </div>
      </div>

      <div class="flex items-center justify-between gap-2 border-t border-neutral-100 px-3 py-2 dark:border-neutral-800">
        <ColorPicker v-model="color" />
        <div class="flex items-center gap-0.5">
          <button
            v-if="!note.trashed"
            type="button"
            class="icon-btn"
            title="Add image"
            aria-label="Add image"
            @click="pickImage"
          >
            <Icon name="image" />
          </button>
          <input
            ref="fileInput"
            type="file"
            accept="image/png,image/jpeg,image/gif,image/webp"
            class="hidden"
            @change="onFileChange"
          />
          <button
            v-if="!note.trashed"
            type="button"
            class="icon-btn"
            :class="liveNote.kind === 'list' ? 'text-brand-700 dark:text-brand' : ''"
            :title="liveNote.kind === 'list' ? 'Convert to text note' : 'Convert to checklist'"
            @click="toggleKind"
          >
            <Icon name="checkbox" />
          </button>
          <LabelPicker v-if="!note.trashed" :model-value="labelList" @update:model-value="onLabelsChange" />
          <template v-if="!note.trashed">
            <button
              type="button"
              class="icon-btn"
              :class="note.pinned ? 'text-brand-700 dark:text-brand' : ''"
              :title="note.pinned ? 'Unpin' : 'Pin'"
              @click="act(() => notes.setPinned(note.id, !note.pinned))"
            >
              <Icon name="pin" />
            </button>
            <button
              type="button"
              class="icon-btn"
              :title="note.archived ? 'Unarchive' : 'Archive'"
              @click="act(() => notes.setArchived(note.id, !note.archived))"
            >
              <Icon name="archive" />
            </button>
            <button type="button" class="icon-btn" title="Move to trash" @click="act(() => notes.trash(note.id))">
              <Icon name="trash" />
            </button>
          </template>
          <template v-else>
            <button type="button" class="icon-btn" title="Restore" @click="act(() => notes.restore(note.id))">
              <Icon name="restore" />
            </button>
            <button
              type="button"
              class="icon-btn"
              title="Delete forever"
              @click="act(() => notes.deleteForever(note.id))"
            >
              <Icon name="trash" />
            </button>
          </template>
          <button
            type="button"
            class="rounded-md px-3 py-1.5 text-sm font-semibold text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:text-neutral-200 dark:hover:bg-neutral-800"
            @click="close"
          >
            Close
          </button>
        </div>
      </div>
    </div>
  </div>
</template>
