<script setup lang="ts">
import { computed, provide, ref, watch } from 'vue'
import { useElementFinalSize } from '../../composables/useElementFinalSize'
import FdsBlockContent from '../Blocks/FdsBlockContent/FdsBlockContent.vue'
import FdsInput from '../Form/FdsInput/FdsInput.vue'
import FdsMeta from '../Typography/FdsMeta/FdsMeta.vue'
import TreeNode from './TreeNode.vue'
import type { FdsNodeShape, FdsTreeNode as TreeNodeType, WrapperProps } from './types'
import useTreeState from './useTreeState'
import { getTitleFromProperties } from './utils'

const props = withDefaults(defineProps<WrapperProps<Record<string, unknown>>>(), {
  indentation: 36,
  showNodeDescription: false,
  showChildrenCount: false,
  expandChildrenOnSelect: false,
  showIndeterminate: false,
  expandChildrenOnParentCheck: false,
  expandAllChildrenOnParentCheck: true,
  showIndeterminateOnlyOnChildrenSelection: false,
  searchEnabled: false,
  searchExpandNodes: false,
  horizontalScroll: false,
  searchMatchParams: () => ['title', 'nodeId'],
  titleTemplate: '[[title]]',
  dataTestid: undefined,
})

const emit = defineEmits<{
  (e: 'update:selectedNodes', value: FdsNodeShape[]): void
  (e: 'getSearchContainerHeight', value: number): void
}>()

const rootNode = props.data
const nodesForState = rootNode ? [rootNode] : []

const treeState = useTreeState({
  expandChildrenOnParentCheck: props.expandChildrenOnParentCheck,
  expandAllChildrenOnParentCheck: props.expandAllChildrenOnParentCheck,
  showIndeterminateOnlyOnChildrenSelection: props.showIndeterminateOnlyOnChildrenSelection,
  searchExpandNodes: props.searchExpandNodes,
  titleTemplate: props.titleTemplate,
  nodes: nodesForState,
  searchInputTriggerLength: props.searchInputTriggerLength,
})
provide('treeState', treeState)

const prunedNodeCache = new WeakMap<TreeNodeType, FdsNodeShape>()
const pruneNodeCached = (node: TreeNodeType): FdsNodeShape => {
  const cached = prunedNodeCache.get(node)
  if (cached) return cached
  const pruned: FdsNodeShape = {
    nodeId: node.nodeId,
    title: node.title,
    data: node.data,
    children: Array.isArray(node.children) ? node.children.map(pruneNodeCached) : undefined,
  }
  prunedNodeCache.set(node, pruned)
  return pruned
}
const getPrunedSelectedNodes = (): FdsNodeShape[] => treeState.selectedNodeObjects.map(pruneNodeCached)

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

useElementFinalSize(searchContainerRef, (height) => {
  emit('getSearchContainerHeight', height)
})

defineExpose({
  selectedNodeObjects: computed<FdsNodeShape[]>(() => getPrunedSelectedNodes()),
  selectedNodes: treeState.selectedNodes,
  getSearchContainerHeight: () => searchContainerRef.value?.getBoundingClientRect().height,
})

watch(
  () => treeState.selectionVersion.value,
  () => {
    emit('update:selectedNodes', getPrunedSelectedNodes())
  },
  { flush: 'post' },
)

// Add 1 to the total node count to include the root node
const totalNodeCount = computed(() => 1 + (treeState?.totalNodeCount.value ?? 0))

const rootAttrs = computed(() => {
  const attrs: Record<string, string> = {}
  if (props.dataTestid) {
    attrs['data-testid'] = props.dataTestid
  }
  return attrs
})
</script>

<template>
  <div v-bind="rootAttrs">
    <div v-if="props.searchEnabled" ref="searchContainerRef" class="pb-4">
      <FdsInput
        :value="treeState?.searchTerm?.value ?? ''"
        @input="treeState?.setSearchTerm(($event.target as HTMLInputElement).value)"
        class="w-full"
        :label="props.searchLabel"
        clear-button
        @clearInput="treeState?.clearSearch()"
      >
        <input id="tree-view-search-input" />
      </FdsInput>
      <div v-if="props.searchCountTemplate || props.searchCountTemplateUnfiltered">
        <h2
          v-if="
            props.searchCountTemplate &&
            treeState?.searchTerm?.value &&
            String(treeState?.searchTerm?.value || '').trim().length >= (treeState?.triggerLength ?? 1)
          "
          class="text-xl"
        >
          {{
            props.searchCountTemplate
              .replace('[[filteredNodes]]', treeState?.filteredMatchCount.value.toString())
              .replace('[[totalNodes]]', totalNodeCount.toString())
          }}
        </h2>
        <h2 class="text-xl" v-else>
          {{
            props.searchCountTemplateUnfiltered
              ?.replace('[[totalNodes]]', totalNodeCount.toString())
              .replace('[[totalNodes]]', totalNodeCount.toString())
          }}
        </h2>
      </div>
    </div>

    <FdsBlockContent v-if="treeState?.isEmptySearchResult?.value" class="mt-4">
      <template #header>{{ props.searchResultNoMatchesTitle }}</template>
      <FdsMeta>{{ props.searchResultNoMatchesBody }}</FdsMeta>
    </FdsBlockContent>

    <div :class="['rounded-md', { 'overflow-x-auto bg-white-solid': props.horizontalScroll }]">
      <TreeNode
        v-if="props.data && !treeState?.isEmptySearchResult?.value"
        :style="{ container: 'bg-white-solid px-3 py-2' }"
        :nodes="props.data.children || []"
        :title="getTitleFromProperties(props.data, props.titleTemplate)"
        :nodeId="props.data.nodeId"
        :data="props.data.data"
        :indentation="props.indentation"
        :showNodeDescription="props.showNodeDescription"
        :showChildrenCount="props.showChildrenCount"
        :expandChildrenOnSelect="props.expandChildrenOnSelect"
        :showIndeterminate="props.showIndeterminate"
        :expandChildrenOnParentCheck="props.expandChildrenOnParentCheck"
        :expandAllChildrenOnParentCheck="props.expandAllChildrenOnParentCheck"
        :showIndeterminateOnlyOnChildrenSelection="props.showIndeterminateOnlyOnChildrenSelection"
        :searchEnabled="props.searchEnabled"
        :searchLabel="props.searchLabel"
        :searchExpandNodes="props.searchExpandNodes"
        :searchMatchParams="props.searchMatchParams"
        :horizontalScroll="props.horizontalScroll"
        :nodeExpandIcon="props.nodeExpandIcon"
        :nodeCollapseIcon="props.nodeCollapseIcon"
        :searchHighlighting="props.searchHighlighting"
        :titleTemplate="props.titleTemplate"
        :popoverLabels="props.popoverLabels"
      />
    </div>
  </div>
</template>
