<script setup lang="ts">
import { flip, offset, shift, useFloating } from "@floating-ui/vue"
import { computed, nextTick, ref, watch } from "vue"
import type { FormattingHandlers } from "@/lib/formatting"
import EditorToolbarContent from "./EditorToolbarContent.vue"

/**
 * Layout mode for the toolbar:
 * - `fixed`: Sticky bar at the editor top.
 * - `bubble`: Floating popover near text selection.
 * - `floating`: Floating popover at the cursor on empty lines.
 */
export type ToolbarMode = "fixed" | "bubble" | "floating"

type PromptKind = "page" | "block" | "tag"

const props = defineProps<{
  /** Toolbar layout mode. Defaults to `fixed`. */
  mode?: ToolbarMode
  /** Formatting handlers from the focused block, or null when no block is focused. */
  handlers: FormattingHandlers | null
  /** Bumped on every cursor move — forces reactivity on active state checks. */
  selectionVersion?: number
  /** Whether undo is available in the operation history. */
  canUndo?: boolean
  /** Whether redo is available in the operation history. */
  canRedo?: boolean
  /** Undo the last operation. */
  undo?: () => void
  /** Redo the last undone operation. */
  redo?: () => void
}>()

const floatingRef = ref<HTMLElement | null>(null)

/**
 * Target element for the bubble/floating toolbar teleport.
 * Teleports into the nearest `.editor-shell` ancestor to keep the toolbar
 * in the editor's stacking context (below app-level overlays).
 * Falls back to `document.body`.
 *
 * In `fixed` mode no teleport is used (the toolbar renders inline).
 */
const teleportTarget = computed(() => {
  if (props.mode === "fixed" || props.mode === undefined) return undefined
  return document.querySelector(".editor-shell") ?? "body"
})

/**
 * Virtual reference element for Floating UI positioning.
 * Provides a `getBoundingClientRect` that tracks the current selection's
 * screen coordinates. Updated reactively when `selectionVersion` changes.
 * Set to `null` to hide the bubble/floating toolbar.
 */
const virtualRef = ref<{
  getBoundingClientRect: () => DOMRect
} | null>(null)

/**
 * Track selection changes and update the floating toolbar position.
 * - In `bubble` mode: show when text is selected, hide when collapsed.
 * - In `floating` mode: show at cursor position regardless of selection state.
 * - Hides when there is no selection (window.getSelection is empty).
 */
watch(
  () => props.selectionVersion,
  () => {
    if (props.mode === "fixed" || props.mode === undefined) return

    const sel = window.getSelection()
    if (!sel?.rangeCount) {
      virtualRef.value = null
      return
    }

    if (props.mode === "bubble" && sel.isCollapsed) {
      virtualRef.value = null
      return
    }

    const rect = sel.getRangeAt(0).getBoundingClientRect()
    virtualRef.value =
      rect.width > 0 || rect.height > 0
        ? { getBoundingClientRect: () => rect }
        : null
  },
  { immediate: true },
)

/**
 * Close bubble/floating toolbar when the focused block loses focus
 * (handlers becomes null). This covers the case where the insertion zone
 * is clicked and no selectionVersion change fires.
 */
watch(
  () => props.handlers,
  (h) => {
    if (!h && props.mode !== "fixed" && props.mode !== undefined) {
      virtualRef.value = null
    }
  },
)

/**
 * Floating UI positioning for bubble/floating modes.
 * Uses the virtual element as the anchor and auto-updates on scroll/resize.
 * - `offset(8)`: 8px gap between toolbar and selection.
 * - `flip()`: flip to bottom when there's no space above.
 * - `shift()`: keep within viewport horizontally.
 */
const { floatingStyles } = useFloating(virtualRef, floatingRef, {
  placement: "top",
  middleware: [offset(8), flip(), shift({ crossAxis: true })],
})

const promptKind = ref<PromptKind | null>(null)
const promptValue = ref("")
const inputRef = ref<any>(null)

const promptOpen = computed({
  get: () => promptKind.value !== null,
  set: (val: boolean) => {
    if (!val) promptKind.value = null
  },
})

const promptLabels = computed(() => ({
  title:
    promptKind.value === "page"
      ? "Insert Page Reference"
      : promptKind.value === "block"
        ? "Insert Block Reference"
        : "Insert Tag",
  placeholder:
    promptKind.value === "page"
      ? "Enter page name..."
      : promptKind.value === "block"
        ? "Enter block ID..."
        : "Enter tag name...",
}))

function openPrompt(kind: PromptKind) {
  promptValue.value = ""
  promptKind.value = kind
  nextTick(() => inputRef.value?.inputRef?.focus())
}

function confirmPrompt() {
  const kind = promptKind.value
  const val = promptValue.value.trim()
  if (!kind || !val || !props.handlers) {
    promptKind.value = null
    return
  }
  if (kind === "page") props.handlers.insertPageRef(val)
  else if (kind === "block") props.handlers.insertBlockRef(val)
  else if (kind === "tag") props.handlers.insertTag(val)
  promptKind.value = null
}

function cancelPrompt() {
  promptKind.value = null
}
</script>

<template>
  <div
    v-if="mode === 'fixed' || mode === undefined"
    role="toolbar"
    aria-label="Formatting toolbar"
    data-slot="base"
    class="flex items-stretch gap-1.5"
    @mousedown.prevent
  >
    <EditorToolbarContent
      :handlers="handlers"
      :selection-version="selectionVersion"
      :can-undo="canUndo"
      :can-redo="canRedo"
      :undo="undo"
      :redo="redo"
      @open-prompt="openPrompt"
    />
  </div>

  <Teleport v-else :to="teleportTarget">
    <div
      v-if="virtualRef"
      ref="floatingRef"
      role="toolbar"
      aria-label="Formatting toolbar"
      data-slot="base"
      data-editor-overlay
      class="flex items-stretch gap-1.5 rounded-lg border border-default bg-(--ui-bg) shadow-lg p-1"
      :style="floatingStyles"
      @mousedown.prevent
    >
      <EditorToolbarContent
        :handlers="handlers"
        :selection-version="selectionVersion"
        :can-undo="canUndo"
        :can-redo="canRedo"
        :undo="undo"
        :redo="redo"
        @open-prompt="openPrompt"
      />
    </div>
  </Teleport>

  <UModal v-model:open="promptOpen">
    <template #content>
      <UCard>
        <template #header>
          <h3 class="text-sm font-semibold">{{ promptLabels.title }}</h3>
        </template>
        <UInput
          ref="inputRef"
          v-model="promptValue"
          :placeholder="promptLabels.placeholder"
          @keydown.enter="confirmPrompt"
          @keydown.escape="cancelPrompt"
        />
        <template #footer>
          <div class="flex justify-end gap-2">
            <UButton label="Cancel" color="neutral" variant="ghost" size="sm" @click="cancelPrompt" />
            <UButton label="Insert" color="primary" size="sm" :disabled="!promptValue.trim()" @click="confirmPrompt" />
          </div>
        </template>
      </UCard>
    </template>
  </UModal>
</template>
