<script setup lang="ts">
import { computed, inject, nextTick, reactive, ref, watch } from 'vue'
import { useFdsI18n } from '../../plugin/useFdsI18n'
import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
import FdsCheckbox from '../Form/FdsCheckbox/FdsCheckbox.vue'
import FdsRadio from '../Form/FdsRadio/FdsRadio.vue'
import type { FdsTreeViewProps } from './types'
import useTreeState from './useTreeState'
import { getTitleFromProperties } from './utils'

type Props<T = Record<string, unknown>> = FdsTreeViewProps<T>

const props = withDefaults(defineProps<Props>(), {
  nodes: () => [],
  depth: 0,
  showNodeDescription: false,
  showChildrenCount: false,
  expandChildrenOnSelect: false,
  showIndeterminate: false,
  expandChildrenOnParentCheck: false,
  expandAllChildrenOnParentCheck: true,
  showIndeterminateOnlyOnChildrenSelection: false,
  searchEnabled: false,
  searchExpandNodes: false,
  horizontalScroll: false,
  searchMatchParams: () => ['title', 'nodeId'],
  searchHighlighting: false,
})

defineOptions({
  name: 'TreeNode',
})

const injectedTreeState = inject<ReturnType<typeof useTreeState> | null>('treeState', null)
const treeState = injectedTreeState!
const expandedNodes = treeState?.expandedNodes ?? reactive(new Set())
const { t } = useFdsI18n()

const childrenToggleAriaLabel = (title: string) => {
  const hasChildren = hasChildrenNodes.value
  const isExpanded = expandedNodes.has(props.nodeId)
  if (hasChildren && isExpanded) {
    return t('FdsTreeView.collapseNode', { title })
  }
  return t('FdsTreeView.expandNode', { title })
}

const hasChildrenNodes = computed(() => {
  const nodesToCheck = props.searchEnabled && treeState?.searchTerm?.value ? filteredNodes.value : props.nodes
  return Array.isArray(nodesToCheck) && nodesToCheck.length > 0
})

const indentValue = props.depth * props.indentation

const indent = computed(() => ({
  flexDirection: 'row' as const,
  alignItems: 'center' as const,
  paddingLeft: `${indentValue + 4}px`,
}))

const filteredNodes = computed(() => {
  const t = String(treeState?.searchTerm?.value || '').trim()
  if (!props.searchEnabled || !t || t.length < (treeState?.triggerLength ?? 1)) {
    return props.nodes
  }
  return treeState.filterNodes(props.nodes, t, props.searchMatchParams)
})

const trimmedSearchTerm = computed(() => String(treeState?.searchTerm?.value || '').trim())
const meetsSearchTrigger = computed(() => trimmedSearchTerm.value.length >= (treeState?.triggerLength ?? 1))
const isSearchActive = computed(() => props.searchEnabled && meetsSearchTrigger.value)
const shouldHighlightSearch = computed(() => props.searchHighlighting && isSearchActive.value)
const childCount = computed(() => {
  if (!props.showChildrenCount || !hasChildrenNodes.value) return null
  return isSearchActive.value ? (filteredNodes.value?.length ?? 0) : props.nodes.length
})
const nodeIsSelected = computed(() => treeState?.isNodeSelected(props.nodeId) ?? false)
const nodeIsIndeterminate = computed(() =>
  props.showIndeterminate ? (treeState?.isNodeIndeterminateById(props.nodeId) ?? false) : false,
)
const shouldShowPopoverActions = computed(
  () => (treeState?.isParentOrChildrenSelected(props.nodeId) ?? false) && !nodeIsIndeterminate.value,
)
const toggleNodeIcon = computed(
  () => treeState?.getNodeIcon(props.nodeId, props.nodeExpandIcon, props.nodeCollapseIcon) ?? props.nodeExpandIcon,
)

// Auto-expand current node if search is active and it has matching children
watch(
  () => ({
    enabled: props.searchEnabled,
    expandOnSearch: props.searchExpandNodes,
    term: treeState?.searchTerm?.value,
    count: (filteredNodes.value || []).length,
  }),
  ({ enabled, expandOnSearch, term, count }) => {
    if (!enabled || !expandOnSearch) return
    const t = String(term || '').trim()
    if (!t || t.length < (treeState?.triggerLength ?? 1)) return
    if (typeof count === 'number' && count > 0) {
      expandedNodes.add(props.nodeId)
    }
  },
  { immediate: true },
)

const isOpen = ref(false)
const triggerRef = ref<HTMLElement | null>(null)
const popoverPanelRef = ref<HTMLElement | null>(null)
const popoverCoords = reactive({ bottom: 0, right: 0, left: 0, top: 0 })

const popoverWidth = 327
const popoverHeight = 80

const computePopoverCoords = () => {
  const componentInstance = triggerRef.value
  if (!componentInstance) return
  // Get the actual DOM element from the Vue component instance
  const triggerEl = '$el' in componentInstance ? (componentInstance as { $el: HTMLElement }).$el : componentInstance
  if (!(triggerEl instanceof HTMLElement)) return
  const rect = triggerEl.getBoundingClientRect()
  popoverCoords.bottom = Math.round(rect.bottom)
  popoverCoords.right = Math.round(rect.right)
  popoverCoords.left = Math.round(rect.left)
  popoverCoords.top = Math.round(rect.top)
}

const openPopover = async () => {
  computePopoverCoords()
  isOpen.value = true
  window.addEventListener('scroll', computePopoverCoords, true)
  window.addEventListener('resize', computePopoverCoords)
  document.addEventListener('pointerdown', handleGlobalPointerDown, { passive: true })
  document.addEventListener('keydown', handleGlobalKeydown)

  // Focus first radio inside the popover for immediate keyboard use
  await nextTick()
  const firstRadio = popoverPanelRef.value?.querySelector('input[type="radio"]') as HTMLInputElement | null
  firstRadio?.focus()
}

const closePopover = () => {
  isOpen.value = false
}

const onAfterPopoverLeave = () => {
  window.removeEventListener('scroll', computePopoverCoords, true)
  window.removeEventListener('resize', computePopoverCoords)
  document.removeEventListener('pointerdown', handleGlobalPointerDown)
  document.removeEventListener('keydown', handleGlobalKeydown)
}

const togglePopover = () => {
  if (isOpen.value) closePopover()
  else openPopover()
}

const handleGlobalPointerDown = (event: PointerEvent) => {
  if (!isOpen.value) return

  const hasComposedPath = (ev: Event): ev is Event & { composedPath: () => EventTarget[] } =>
    typeof (ev as { composedPath?: () => EventTarget[] }).composedPath === 'function'

  const clickedPath = hasComposedPath(event) ? event.composedPath() : ([] as EventTarget[])
  const contains = (el: HTMLElement | null) => (el ? clickedPath.includes(el) : false)

  // Extract DOM element from component instance if needed
  const getTriggerElement = (): HTMLElement | null => {
    const instance = triggerRef.value
    if (!instance) return null
    return '$el' in instance
      ? (instance as { $el: HTMLElement }).$el
      : instance instanceof HTMLElement
        ? instance
        : null
  }

  if (!contains(getTriggerElement()) && !contains(popoverPanelRef.value)) {
    closePopover()
  }
}

const handleGlobalKeydown = (event: KeyboardEvent) => {
  if (event.key === 'Escape' && isOpen.value) {
    closePopover()
  }
}

const onPanelFocusOut = (event: FocusEvent) => {
  const next = event.relatedTarget as HTMLElement | null
  const panelEl = popoverPanelRef.value
  if (!panelEl) return

  const triggerVal = triggerRef.value as unknown
  const triggerEl =
    triggerVal && typeof triggerVal === 'object' && '$el' in (triggerVal as Record<string, unknown>)
      ? (triggerVal as { $el: HTMLElement }).$el
      : (triggerVal as HTMLElement | null)

  const isButton = next?.tagName === 'BUTTON' && next?.id !== 'tree-popover-close-button'
  const isInsidePanel = !!next && panelEl.contains(next)
  const isTrigger = !!next && !!triggerEl && triggerEl.contains(next)

  const leavingPanel = !next || (!isInsidePanel && !isTrigger) || isButton
  if (leavingPanel) closePopover()
}

const options = [
  { label: props.popoverLabels?.parentAndChildren, value: 'parent_and_children' },
  { label: props.popoverLabels?.parent, value: 'parent' },
]

const currentOption = computed(() => {
  if (treeState?.isParentAndAllChildrenSelected(props.nodeId)) return 'parent_and_children'
  if (treeState?.isParentOnlySelected(props.nodeId)) return 'parent'
  return ''
})

// Ref for v-model on radio group - matches FdsRadio v-model type (string | number | boolean | null)
const selectedOption = ref<string | number | boolean | null>(currentOption.value)
const isSyncingSelectedOption = ref(false)

// Sync selectedOption when currentOption changes externally
watch(
  currentOption,
  (newValue) => {
    isSyncingSelectedOption.value = true
    selectedOption.value = newValue
    isSyncingSelectedOption.value = false
  },
  { immediate: true },
)

// Watch selectedOption to handle changes
watch(selectedOption, (newValue) => {
  if (isSyncingSelectedOption.value || !isOpen.value) return
  if (newValue && newValue !== currentOption.value) {
    handleOptionClick(newValue as (typeof options)[number]['value'])
  }
})

const handleOptionClick = (option: (typeof options)[number]['value']) => {
  if (option === 'parent_and_children') {
    treeState?.selectNodeAndAllChildren(props.nodeId)
  } else if (option === 'parent') {
    treeState?.deselectChildrenOnly(props.nodeId)
  }
  closePopover()
}
</script>

<template>
  <div :class="props?.style?.container">
    <div :style="indent" :class="'pt-2 pb-2'">
      <div class="flex items-start">
        <FdsButtonIcon
          v-if="hasChildrenNodes"
          @click="treeState?.toggleExpandNode(props.nodeId)"
          v-bind="{ 'aria-label': props.title ? childrenToggleAriaLabel(props.title) : undefined }"
          :icon="toggleNodeIcon"
          :size="24"
          class="fill-blue-500 mr-3 mt-0.5 items-center"
        />
        <div
          v-if="!hasChildrenNodes && treeState?.absoluteTopNodeHasChildren()"
          :style="{ minWidth: `${indentation}px` }"
        />
        <div class="flex-1 flex">
          <FdsCheckbox
            :id="`checkbox-${props.nodeId}`"
            :checked="nodeIsSelected"
            :indeterminate="nodeIsIndeterminate"
            @change="treeState?.toggleSelectNode(props.nodeId, props.title, props.data)"
          >
            <span>
              <span
                v-if="shouldHighlightSearch && title"
                v-html="treeState?.highlightText(title, treeState?.searchTerm?.value)"
              />
              <span v-else>{{ title }}</span>
            </span>
            <span v-if="childCount !== null">{{ t('FdsTreeView.childrenCountPrefix') }}{{ childCount }})</span>
          </FdsCheckbox>
        </div>
        <div class="items-center max-h-[28px]" v-if="shouldShowPopoverActions">
          <FdsButtonIcon
            ref="triggerRef"
            icon="more"
            :size="24"
            @click="togglePopover"
            v-bind="{ 'aria-label': t('FdsTreeView.moreOptionsForNode', { title: String(title ?? '') }) }"
            class="items-center"
          />

          <Transition
            enter-active-class="transition-opacity duration-200 ease-in-out"
            enter-from-class="opacity-0"
            enter-to-class="opacity-100"
            leave-active-class="transition-opacity duration-500 ease-in-out"
            leave-from-class="opacity-100"
            leave-to-class="opacity-0"
            @after-leave="onAfterPopoverLeave"
          >
            <div
              v-if="isOpen"
              class="fixed z-9999"
              :style="{
                top: popoverCoords.top + 'px',
                left: popoverCoords.right - popoverWidth - (popoverCoords.right - popoverCoords.left) + 'px',
              }"
            >
              <div
                ref="popoverPanelRef"
                class="bg-white border border-blue-500 rounded-md p-4 shadow-lg flex flex-row justify-between items-start gap-3"
                :style="{
                  width: popoverWidth + 'px',
                  minHeight: popoverHeight + 'px',
                }"
                @click.stop
                @focusout="onPanelFocusOut"
              >
                <div class="flex flex-col gap-2">
                  <div
                    v-for="option in options"
                    :key="option.value"
                    @click="
                      (e) => {
                        if ((e.target as HTMLElement).tagName !== 'INPUT') {
                          handleOptionClick(option.value)
                        }
                      }
                    "
                    class="cursor-pointer"
                  >
                    <FdsRadio
                      v-model="selectedOption"
                      :id="`tree-popover-${props.nodeId}-${option.value}`"
                      :name="`tree-popover-${props.nodeId}`"
                      :value="option.value"
                      @blur="closePopover"
                      :label="option.label"
                    />
                  </div>
                </div>
                <FdsButtonIcon
                  icon="cross"
                  id="tree-popover-close-button"
                  :size="24"
                  @click="closePopover()"
                  class="fill-blue-500"
                  v-bind="{ 'aria-label': t('FdsTreeView.closePopover') }"
                />
              </div>
            </div>
          </Transition>
        </div>
      </div>
    </div>

    <div v-if="expandedNodes.has(props.nodeId)">
      <TreeNode
        v-for="child in filteredNodes"
        :key="child.nodeId"
        :nodes="child.children || []"
        :title="getTitleFromProperties(child, props.titleTemplate)"
        :nodeId="child.nodeId"
        :data="child.data"
        :depth="props.depth + 1"
        :indentation="props.indentation"
        :nodeExpandIcon="props.nodeExpandIcon"
        :nodeCollapseIcon="props.nodeCollapseIcon"
        :showNodeDescription="showNodeDescription"
        :showChildrenCount="showChildrenCount"
        :expandChildrenOnSelect="expandChildrenOnSelect"
        :showIndeterminate="showIndeterminate"
        :showIndeterminateOnlyOnChildrenSelection="showIndeterminateOnlyOnChildrenSelection"
        :expandChildrenOnParentCheck="expandChildrenOnParentCheck"
        :expandAllChildrenOnParentCheck="expandAllChildrenOnParentCheck"
        :searchEnabled="searchEnabled"
        :searchExpandNodes="searchExpandNodes"
        :searchMatchParams="searchMatchParams"
        :searchHighlighting="searchHighlighting"
        :titleTemplate="props.titleTemplate"
        :popoverLabels="props.popoverLabels"
      />
    </div>
  </div>
</template>
