<script lang="tsx">
import { type AxiosResponse } from 'axios'
import {
  computed,
  defineComponent,
  nextTick,
  onBeforeUnmount,
  onMounted,
  provide,
  ref,
  watch, type ComputedRef,
  type PropType,
  type Ref,
} from 'vue'
import { setCountSpaces } from '../../../helpers'
import type ApiInterface from '../../../types/api-interface'
import ExpandButton from '../../buttons/expand-button/ARKExpandButton.vue'
import {
  type ColumnInterface,
  type DefaultTableContextActionInterface,
  type DefaultTableContextMenuInterface,
  type TableSettingsInterface,
  type TreeTableInterface,
} from '../DefaultTableInterfaces'

import { ActionBar, type ActionBarActionInterface } from '../../action-bar'
import CheckBox from '../../checkbox/ARKCheckbox.vue'
import DataLoader from '../../data-loader/ARKDataLoader.vue'
import RadioButton from '../../radio-button/RadioButton.vue'
import ARKHeader from '../ARKHeader.vue'
import ContextMenu from '../ContextMenu.vue'
import DragAndDropItem from '../drag-and-drop/DragAndDropItem.vue'
import { type DragAndDropSortingSettings } from '../drag-and-drop/DragAndDropItemInterfaces'
import DefaultHeaders from '../headers/DefaultHeaders.vue'
import NoData from '../no-data/NoData.vue'

import { ArkUiConstants } from '../../../composables/use-ark-ui'
import useCheckedItems, { type CheckedItemsInterface } from '../../../composables/use-checked-items'
import useMotionTracker from '../../../composables/use-motion-tracker'
import { type QuerySerializationStrategy } from '../../../types/QuerySerializationStrategies/QuerySerializationStrategy'
import DefaultButton from '../../buttons/DefaultButton.vue'
import PaginationTrigger from './PaginationTrigger.vue'
import useColumns from '../composables/useColumns'
import useNodes, { type NodeItem, type NodeItemExtended } from '../composables/useNodes'
import useSort from '../composables/useSort'
import { prepareSmartFilterParams } from '../headers/headers/prepareSmartFilterParams'
import type DataProviderInterface from '../../../types/data-provider-interface'
import LineLoader from '../../line-loader/ARKLineLoader.vue'

interface NodeItemResponse<R = NodeItem> {
  count: number
  results: R[]
}

/**
 * Компонент требует получения данных из API.
 * <br />
 * Компонент представляет из себя таблицу, которая позволяет отображать в себе элементы, которые в свою очередь имеют в себе дочерние элементы.
 * <br />
 * Таким образом таблица отображает в себе элементы с бесконечной глубиной вложенности.
 * <br />
 * Вложенные элементы раскрываются у родителя через специальную кнопку, отображающуюся возле родителя, здесь важно понимать, что изначально вложенные элементы в родителе не присутствуют. Они подгружаются в момент самого раскрытия родителя.
 * <br />
 * Так же таблица позволяет выбирать конкретные ее элементы, а затем делать с ними какие-либо действия через 'Action bar'.
 *
 * <ul>
 * <li><a href="https://www.figma.com/file/Q4Z4CnFx4i0qhD5fbgnPpf/%F0%9F%92%BB-%D0%94%D0%B8%D0%B7%D0%B0%D0%B9%D0%BD-%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0-ARK-SPACE?type=design&node-id=4959%3A76222&mode=design&t=Y0Pj8B0S1amFe0ZG-1" target="_blank">Дизайн</a></li>
 * </ul>
 */
export default defineComponent({
  // добавляем пропсы в алфавитном порядке
  props: {
    /**
     * Отображение столбца с кнопками "Развернуть\Свернуть" строку (для полей с "еще")
     */
    isExpandRowButton: {
      type: Boolean,
      default: false,
    },
    /**
     * Принудительное скрытие 'Action bar'а
     */
    isHideActionBar: {
      type: Boolean,
      default: false,
    },
    /**
     * Принудительное скрытие контекстного меню
     */
    isHideContextMenu: {
      type: Boolean,
      default: false,
    },

    /**
     * Массив действий, отображаемых в 'Action bar'е
     */
    actions: {
      type: Array as PropType<ActionBarActionInterface[]>,
      default: () => [],
    },

    /**
     * Опция применения фильтрации к дочерним элементам
     * В ситуации поиска по таблице, по умолчанию родительские элементы не отображаются, если они не удовлетворяют запросам поиска.
     * Данная опция позволяет отображать родителей при поиске, даже если они не удовлетворяют запросам поиска.
     */
    applyFiltersOnChildrenQuery: {
      type: Boolean,
      default: true,
    },

    /**
     * Интерфейс API
     */
    api: {
      type: Object as PropType<ApiInterface>,
    },

    /**
     * Массив кнопок, отображающихся справа над таблицей
     */
    buttons: {
      type: Function as PropType<() => JSX.Element>,
    },

    /**
     * Ключ кеширования настроек колонок таблицы
     */
    cacheKey: {
      type: String,
      default: undefined,
    },

    /**
     * Выбор элементов в таблице
     *
     * @param 'one' - только один элемент
     * @param 'many' - множественный выбор
     * @param 'none' - выключен
     */
    canCheck: {
      type: String as PropType<'one'|'many'|'none'>,
      default: 'none',
    },

    /**
     * Callback для определения отображения кнопки "Развернуть\Свернуть" (для полей с "еще")
     */
    canExpandRow: {
      type: Function as PropType<(item: any) => boolean>,
      default: () => true,
    },

    /**
     * Отображать кнопку для сворачивания всех развернутых веток
     */
    canHideAll: {
      type: Boolean,
    },

    /**
     * Callback для определения блокировки чекбокса в строке
     * Принимает в себя элемент типа 'NodeItemExtended'
     * Аналог в DefaultTable - canCheckExactItem
     */
    canItemBeChecked: {
      type: Function as PropType<(item: any) => boolean>,
      default: () => true,
    },

    /**
     * Настройка для снятия выделений после фильтрации
     */
    clearCheckedOnParamsChange: {
      type: Boolean,
      default: true,
    },

    /**
     * Массив колонок или фильтров для шапки таблицы
     */
    columns: {
      type: Array as PropType<ColumnInterface[]>,
      required: true,
    },

    /**
     * Массив действий контекстного меню для выбранной строки
     */
    contextActions: {
      type: Array as PropType<DefaultTableContextActionInterface[]>,
      default: () => [],
    },

    /**
     * Массив по умолчанию выделенных элементов
     */
    defaultCheckedItems: {
      type: Array as PropType<any[]>,
      default: () => [],
    },

    /**
     * Дефолтные квери параметры
     */
    defaultQueryParams: {
      type: Object as PropType<Record<string, string|string[]>>,
      default: () => ({}),
    },

    /**
     * Сортировка по указанному ключу
     * Тип сортировки 'ASC' или 'DESC' настраивается нажатием на соответствующую настройку, отображающуюся возле названия заголовка колонки
     */
    defaultSort: {
      type: String,
      default: 'id',
    },

    /**
     * Настройка скрытия шапки всей таблицы, где отображается ее заголовок и кнопки
     */
    disableTop: {
      type: Boolean,
      default: false,
    },

    /**
     * Массив `id` строк, которых необходимо заблокировать
     */
    disabledItems: {
      type: Array as PropType<NonNullable<NodeItem['id']>[]>,
      default: undefined,
    },

    /**
     * Интерфейс сортировки элементов при их перетаскивании
     * Так же активирует опцию перетаскивания элементов
     */
    dragAndDropSorting: {
      type: Object as PropType<DragAndDropSortingSettings>,
    },

    /**
     * Интерфейс dataProvider \
     * Пользовательский способ получения данных, включая локальные данные
     */
    dataProvider: {
      type: Object as PropType<DataProviderInterface>,
      default: undefined,
    },

    /**
     * Эндпоинт, на который будут отправляться запросы на добавление / удаление файла
     */
    endpoint: {
      type: String,
      required: true,
    },

    /**
     * Опция включения отображения количества выделенных элементов в 'Action bar'е
     */
    isCounterVisible: {
      type: Boolean,
      default: false,
    },

    /**
     * Опция отключения выбора дочерних элментов при выборе родителя
     */
    isCheckedWithChildrens: {
      type: Boolean,
      default: true,
    },

    /**
     * Опция использования пагинации
     */
    isUsePagination: {
      type: Boolean,
      default: false,
    },

    /**
     * Функция, в которой можно привести массив элементов к нужному формату
     */
    itemConverter: {
      type: Function as PropType<(item: any) => NodeItem>,
      default: (i: any) => i,
    },

    /**
     * Опция изменения текста заглушек
     */
    noDataText: {
      type: String,
      default: '',
    },

    /**
     * Callback для кнопки в заглушке.
     * При отсутствии пропса эта кнопка не отображается.
     */
    noDataButtonClick: {
      type: Function as () => any | undefined,
    },

    /**
     * Функция, запускающаяся при выборе элементов
     */
    onCheckItem: {
      type: Function as PropType<(items: any[]) => void>,
      default: () => {
        // do nothing
      },
    },

    /**
     * Хук, запускающийся при первой загрузке элементов
     * Принимает в себя значение нового количества загруженных элементов
     */
    onInitialCountUpdate: {
      type: Function as PropType<(v: number) => void>,
    },

    /**
     * Хук, запускающийся при клике на строку
     */
    onRowClick: {
      type: Function as PropType<(item: any) => void>,
    },

    /**
     * Хук, запускающийся при обновлении количества элементов
     */
    onTableCountUpdate: {
      type: Function as PropType<(v: number) => void>,
    },

    /**
     * Ключ ID родителя для его дочернего элемента
     */
    parentKey: {
      type: String,
      default: 'parent_id',
    },

    /**
     * Объект, в который прокидывается экземпляр класса QuerySerializationStrategy со своей функцией сериализации параметров - serialize
     */
    queryParamsSerializationStrategy: {
      type: Object as PropType<QuerySerializationStrategy>,
    },

    /**
     * Ключ пагинации текущей страницы
     */
    requestPageKey: {
      type: String,
    },

    /**
     * Ключ пагинации куда указывать количество элементов запрашиваемое в запросе к API на одной странице пагинации
     */
    requestPerPageKey: {
      type: String,
    },

    /**
     * Количество элементов запрашиваемое в запросе к API на одной странице пагинации
     */
    requestSize: {
      type: Number,
      default: 10,
    },

    /**
     * Ключ количества всех элементов
     */
    responseTotalKey: {
      type: String,
    },

    /**
     * Опция, позволяющая отображать действия для конкретной строки у самого края справа таблицы
     */
    rowActions: {
      type: Function as PropType<(item: NodeItemExtended) => JSX.Element>,
    },

    /**
     * Опция, позволяющая установить цвет фона строки \
     * Принимает в себя элемент таблицы и порядок элемента в таблице
     */
    setRowColor: {
      type: Function as PropType<(item: NodeItemExtended, index: number) => string>,
      default: undefined,
    },

    /**
     * Настройка вкл/выкл умных фильтров
     */
    smartFilter: {
      type: Boolean,
      default: false,
    },

    /**
     * Настройка отображения количества элементов
     */
    showCount: {
      type: Boolean,
    },

    /**
     * Показывать заглушку таблицы, если по запросу ничего не найдено
     */
    showNodata: {
      type: Boolean,
      default: true,
    },

    /**
     * Настройка блокировки фильтров при отсутствии данных в таблице
     */
    isFilterBlockNoData: {
      type: Boolean,
      default: false,
    },

    /**
     * Позволяет уменьшать размер кнопки "Настройка колонок"
     * при значении true кнопка-шестеренка будет размером 32px
     * по умолчанию false, это 40px
     */
    isSmallSettingsButton: {
      type: Boolean,
      default: false,
    },

    /**
     * Настройка отображения кнопки, настраивающей колонки таблицы
     */
    showSettings: {
      type: Boolean,
      default: true,
    },

    /**
     * Опция отображения маленького названия таблицы
     */
    showSmallName: {
      type: Boolean,
      default: true,
    },

    /**
     * Ключ для сортировки
     */
    sortFieldKey: {
      type: String,
      required: true,
    },

    /**
     * Позволяет блокировать строку и всех ее детей любого уровня вложенности \
     * Указанный ключ должен быть внутри строки (`item`'а), а его значение должно быть `boolean` \
     * Если не указано, то возможность выключена
     */
    disableBranchKey: {
      type: String,
      default: undefined,
    },

    /**
     * Флаг, убирающий кнопку сортировки в заголовке колонки
     */
    enableSorting: {
      type: Boolean,
    },

    /**
     * Заголовок таблицы
     */
    title: {
      type: String,
      required: false,
    },

    /**
     * Опция, позволяющая отображать только родителей
     * Блокирует раскрытие дочерних элементов
     */
    useSingleQuery: {
      type: Boolean,
      default: false,
    },

    /**
     * Интерфейс внешних выбранных элементов
     */
    checkedItemsInterface: {
      type: Object as PropType<CheckedItemsInterface>,
      default: undefined,
    },
  },

  setup(props, context) {
    const api = props.api || ArkUiConstants.api
    if (!api) throw new Error('API is not defined')

    const staticColumns = props.columns.map((c) => ({ ...c, isStatic: true }))
    const dynamicColumns = ref<ColumnInterface[]>([...staticColumns])
    const bothStaticAndDynamicColumns = computed(() => ([...dynamicColumns.value]))

    const { columns, computedColumns, params } = useColumns(bothStaticAndDynamicColumns, props.defaultQueryParams, props.cacheKey)

    const smartFilterParams = computed(() => prepareSmartFilterParams('none', {}, params.value))

    const rootContainer = ref(null) as unknown as Ref<HTMLElement>
    const {
      sortField, sortOrder, computedSortField, setSort,
    } = useSort(props.defaultSort, props.cacheKey)
    const draggedItem: Ref<NodeItemExtended | undefined> = ref(undefined)
    const itemList: Ref<HTMLElement | null> = ref(null)
    const settings = ref(null) as unknown as Ref<TableSettingsInterface>

    const count = ref(0)
    const items = ref<NodeItem[]>([])

    const paginationState = ref<{
      mainList: {
        currentPage: number
        totalCount: number
        isLoadingNextPage: boolean
      }
      childrenLists: Map<string | number, {
        currentPage: number
        totalCount: number
        isLoadingNextPage: boolean
      }>
    }>({
      mainList: {
        currentPage: 1,
        totalCount: 0,
        isLoadingNextPage: false,
      },
      childrenLists: new Map(),
    })

    const {
      DEFAULT_DEPTH, extendedNodeItems, expandedNodeItems, expandedIds, expand, toNodeItemExtended,
    } = useNodes(items, {
      isUseSingleQuery: props.useSingleQuery,
      dragAndDropSorting: props.dragAndDropSorting,

      loadMethod: initialLoad,
    })
    const dataLoaderOutsideActive = ref(true)
    const dataLoaderInsideActive = ref(false)

    const isFiltersSet = computed(() => columns.value.some((c) => c.filter.hasValues()))
    const isUseModalFilters: Ref<boolean> = ref(false)
    const resizeObserver = new ResizeObserver((entry) => {
      const viewportWidth: number = entry[0].target.clientHeight ?? 0
      const viewportHeight: number = entry[0].target.clientHeight ?? 0

      isUseModalFilters.value = viewportWidth <= 900 || viewportHeight <= 500
    })

    const isFilterBlock = computed(() => (count.value === 0 && props.isFilterBlockNoData && !isFiltersSet.value))

    const tableWidth = computed(() => computedColumns.value.reduce((acc, column) => acc + column.width, 0))

    const spanColumns = () => {
      const columnsWidth = tableWidth.value
      const rootWidth = rootContainer.value.scrollWidth < rootContainer.value.offsetWidth ? rootContainer.value.scrollWidth : rootContainer.value.offsetWidth
      if (rootWidth > columnsWidth) {
        const columns = computedColumns.value.filter((column) => !column.wasResized)
        const additionalWidth = (rootWidth - tableWidth.value) / columns.length
        columns.forEach((column) => {
          column.width += additionalWidth
        })
      }
    }

    const updateColumnSettings = () => {
      nextTick(() => {
        // computedColumns.value.forEach(column => {
        // if (!column.wasResized) column.width = 200
        // })

        spanColumns()
      })
    }

    const {
      checkedItems, check, isChecked, clearChecked, checkAll, changeType,
    } = props.checkedItemsInterface ?? useCheckedItems(props.defaultCheckedItems, props.canCheck !== 'none' ? props.canCheck : undefined)

    watch(() => props.canCheck, (newVal, oldVal) => {
      if (newVal === oldVal || (newVal !== 'one' && newVal !== 'many')) return
      changeType(newVal)
    })

    const showActionBar: ComputedRef<boolean> = computed(() => Boolean(checkedItems.value.length))

    const isChildsChecked = (item: NodeItemExtended): boolean => {
      const isNestedChildsChecked = (items: NodeItemExtended[]) => {
        const result: boolean[] = []
        items.forEach((i) => {
          if (isChecked(i)) result.push(true)
          if (i.children) result.push(...isNestedChildsChecked(i.children))
        })

        return result
      }

      return Boolean(isNestedChildsChecked(item.children).length) && props.canCheck === 'many'
    }

    const isHeadCheckboxHalfChecked = computed(() => Boolean(
      checkedItems.value.reduce((acc, { parent }) => (!parent ? acc + 1 : acc), 0),
    ))

    const onCheckTree = (items: NodeItemExtended[], target: boolean) => {
      items.forEach((i) => {
        if (isChecked(i) && !target) check(i, props.onCheckItem)
        if (!isChecked(i) && target) check(i, props.onCheckItem)
        if (i.children) onCheckTree(i.children, target)
      })
    }

    const onCheck = (item: NodeItemExtended) => {
      if (props.isCheckedWithChildrens) {
        const isIndeterminate = isChildsChecked(item)
        const isSelfChecked = !isChecked(item)

        if (!isIndeterminate) {
          check(item, props.onCheckItem)
        }
        if (isIndeterminate && !isSelfChecked) {
          check(item, props.onCheckItem)
        }

        if (item.children && props.canCheck === 'many') {
          onCheckTree(item.children, !isIndeterminate)
        }
      } else check(item, props.onCheckItem)
    }

    const contextMenu: Ref<DefaultTableContextMenuInterface | undefined> = ref()

    const contextMenuElement: Ref<HTMLElement | undefined> = ref()
    const clickedColumnElement: Ref<HTMLElement | undefined> = ref()

    async function onContextmenu(e: MouseEvent, item: any) {
      if (props.isHideContextMenu) return
      e.preventDefault()
      document.body.click()
      await nextTick()
      if (!contextMenu.value) return

      clickedColumnElement.value = e.target as HTMLElement
      contextMenuElement.value = await contextMenu.value.open(
        props.contextActions,
        (action) => action.callback(item),
      )
    }

    useMotionTracker(clickedColumnElement, contextMenuElement, computed((): boolean => Boolean(contextMenuElement.value)))

    const toggleExpandRow = (row: NodeItemExtended) => {
      const index = expandedNodeItems.value.findIndex((item) => item.id === row.id)
      if (index !== -1) {
        expandedNodeItems.value[index].isExpandedRow = !expandedNodeItems.value[index].isExpandedRow
      }
    }

    const horizontalEyesightMargins = computed<number[]>(() => {
      let margin = 0
      if (props.isExpandRowButton) margin += 48
      if (props.canCheck !== 'none') margin += 48

      return [
        margin,
        ...computedColumns.value.map((v) => {
          margin += v.horizontalEyesight ? (v.width || 0) : 0
          return margin
        }),
      ]
    })

    const expandRowMargin = computed<number>(() => (props.canCheck !== 'none' ? 48 : 0))

    const hasHorizontalEyesightColumn = computed<boolean>(() => computedColumns.value.some((column) => column.horizontalEyesight === true))

    const load = async (parent?: NodeItem, page = 1) => {
      let result: NodeItemResponse

      const baseParams = {
        ...smartFilterParams.value,
        [props.sortFieldKey]: computedSortField.value,
      }

      const paginationParams = props.isUsePagination ? {
        ...(props.requestPageKey && { [props.requestPageKey]: page.toString() }),
        ...(props.requestPerPageKey && { [props.requestPerPageKey]: props.requestSize }),
      } : {}

      let loadParams = { ...baseParams, ...paginationParams }
      const strategyLoadParams = { ...smartFilterParams.value }

      if (parent && !props.applyFiltersOnChildrenQuery && parent.id) {
        loadParams = {
          [props.parentKey]: parent.id,
          [props.sortFieldKey]: computedSortField.value,
          ...paginationParams,
        }
        strategyLoadParams[props.parentKey] = parent.id
      }

      if (props.dataProvider) {
        result = await props.dataProvider.get({ params: loadParams })

        if (props.dataProvider.getCount) result.count = await props.dataProvider.getCount({ params: loadParams })
      } else {
        const response: AxiosResponse<NodeItemResponse> = await (
          props.queryParamsSerializationStrategy
            ? api.get(props.endpoint + props.queryParamsSerializationStrategy.serialize(strategyLoadParams))
            : api.get(props.endpoint, { params: loadParams })
        )

        result = response.data
      }

      if (parent?.id) {
        paginationState.value.childrenLists.set(parent.id, {
          currentPage: page,
          totalCount: result.count,
          isLoadingNextPage: false,
        })
      } else {
        paginationState.value.mainList.currentPage = page
        paginationState.value.mainList.totalCount = result.count
      }

      result.results = result.results.map(props.itemConverter)

      return result
    }

    const getTargetState = (parent?: NodeItemExtended) => (
      parent && parent.id
        ? paginationState.value.childrenLists.get(parent.id)
        : paginationState.value.mainList
    )

    const loadNext = async (parent?: NodeItemExtended) => {
      if (!props.isUsePagination) return

      const targetState = getTargetState(parent)

      if (!targetState) return

      const hasMorePages = targetState.currentPage * props.requestSize < targetState.totalCount
      if (!hasMorePages || targetState.isLoadingNextPage) return

      targetState.isLoadingNextPage = true

      try {
        const nextPage = targetState.currentPage + 1
        const result = await load(parent, nextPage)

        targetState.currentPage = nextPage
        targetState.totalCount = result.count

        if (parent) {
          parent.children = [...(parent.children || []), ...result.results.map((i) => toNodeItemExtended(i, parent))]
        } else {
          items.value = [...items.value, ...result.results]
          count.value = Math.max(count.value, result.count)
        }
      } finally {
        targetState.isLoadingNextPage = false
      }
    }

    async function initialLoad(parent?: NodeItem) {
      const data = await load(parent, 1)
      count.value += data.count

      if (props.onInitialCountUpdate) props.onInitialCountUpdate(count.value)

      if (parent) {
        parent.children = data.results
        if (props.isUsePagination && parent.id) {
          paginationState.value.childrenLists.set(parent.id, {
            currentPage: 1,
            totalCount: data.count,
            isLoadingNextPage: false,
          })
        }
      } else {
        items.value = data.results
        if (props.isUsePagination) {
          paginationState.value.mainList.totalCount = data.count
        }
      }

      return data.results
    }

    const reload = async () => {
      itemList.value?.scroll({ top: 0 })

      items.value = []
      count.value = 0
      dataLoaderInsideActive.value = true
      const { results, count: responseCount } = await load()
      const openNode = async (node?: NodeItemExtended) => {
        if (!node) return

        await expand(node)
        await nextTick()
        node.children.forEach((child) => {
          if (typeof child.id !== 'undefined' && expandedIds.has(child.id)) openNode(child)
        })
      }

      items.value = results
      count.value = responseCount
      dataLoaderInsideActive.value = false

      if (props.onTableCountUpdate) {
        props.onTableCountUpdate(count.value)
      }

      await nextTick()
      items.value.forEach((item) => {
        if (typeof item.id !== 'undefined' && expandedIds.has(item.id)) {
          openNode(expandedNodeItems.value.find((i) => i.id === item.id))
        }
      })
    }

    function onParamsChange() {
      if (props.clearCheckedOnParamsChange) {
        clearChecked(props.onCheckItem)
      }

      reload()
    }

    let paramsTriggerDelay: any
    watch(params, () => {
      dataLoaderInsideActive.value = true
      clearTimeout(paramsTriggerDelay)
      paramsTriggerDelay = setTimeout(onParamsChange, 500)
    })

    watch(computedSortField, reload)

    onMounted(async () => {
      resizeObserver.observe(rootContainer.value)

      if ((rootContainer.value.clientWidth > tableWidth.value) && computedColumns.value.length) {
        computedColumns.value[computedColumns.value.length - 1].width += rootContainer.value.clientWidth - tableWidth.value
      }
      await initialLoad()

      dataLoaderOutsideActive.value = true

      dataLoaderOutsideActive.value = false
      spanColumns()
    })

    onBeforeUnmount(() => {
      resizeObserver.disconnect()
    })

    const findParent = (items: NodeItemExtended[], id: string): NodeItemExtended | null => items.reduce((acc: NodeItemExtended | null, item) => {
      if (acc) return acc
      if (item.id === id) return item
      if (item.children?.length) return findParent(item.children, id)
      return null
    }, null)

    const addElement = async (
      item: NodeItem,
      parentId?: NodeItem['id'],
      onAdd?: (items: Ref<NodeItemExtended[]>, parentChilds: NodeItemExtended[]) => void,
    ) => {
      if (parentId) {
        const parentNode = findParent(extendedNodeItems.value, parentId)

        if (parentNode) {
          parentNode.isEmpty = false

          await expand(parentNode, true) // перезагрузка дочерних элементов

          const parentChilds = parentNode.children

          count.value += 1

          if (onAdd) {
            onAdd(extendedNodeItems, parentChilds)
          }
        }
      } else {
        items.value.push(item)

        count.value += 1

        if (onAdd) {
          onAdd(extendedNodeItems, extendedNodeItems.value)
        }
      }
    }

    const reloadAndUpdateCount = async () => {
      await reload()

      if (props.onInitialCountUpdate) {
        await props.onInitialCountUpdate(count.value)
      }
    }

    provide('isUseModalFilters', isUseModalFilters)

    const sortingComputed = computed<Record<string, string>>(() => {
      if (!sortField.value || sortOrder.value === undefined) return {}

      return {
        [sortField.value]: sortOrder.value === false ? 'DESC' : 'ASC',
      }
    })

    context.expose({
      count: count as unknown as typeof count.value,
      items: items as unknown as typeof items.value,
      addElement,
      reload,
      reloadAndUpdateCount,
      clearChecked,
      params: smartFilterParams as unknown as typeof smartFilterParams.value,
      sorting: sortingComputed as unknown as typeof sortingComputed.value,
      settings: settings as unknown as typeof settings.value,
      checkedItems: checkedItems as unknown as typeof checkedItems.value,
      columns: columns as unknown as typeof columns.value,
      computedColumns: computedColumns as unknown as typeof computedColumns.value,
    } as TreeTableInterface)

    function isItemDisabled(item: NodeItemExtended) {
      if (item.id === undefined) return false

      if (props.disabledItems?.length && props.disabledItems.includes(item.id)) {
        return true
      }

      if (props.disableBranchKey) {
        const isDisabledBranch = (item: NodeItemExtended): boolean => {
          let { parent } = item

          while (parent) {
            if (parent[props.disableBranchKey!]) return true
            parent = parent.parent
          }

          return false
        }

        if (item[props.disableBranchKey]) return true
        if (isDisabledBranch(item)) return true
      }

      return false
    }

    function renderElem(can: 'one'|'many'|'none', item: NodeItemExtended): JSX.Element {
      const CheckElement = can === 'many' ? CheckBox : RadioButton

      return <>
        <CheckElement
          isDisabled={!props.canItemBeChecked(item) || isItemDisabled(item)}
          class={{ 'ark-ui-table-multiple-checkbox': isChildsChecked(item) }}
          modelValue={isChecked(item)}
          onValueChange={() => onCheck(item)}
        />
      </>
    }

    const hideAll = (items: NodeItemExtended[]) => {
      items.forEach((item) => {
        item.isExpanded = false
        hideAll(item.children)
      })
    }

    const buttons = () => {
      if (!props.canHideAll) return (props.buttons && props.buttons()) || <div/>
      return (
        <div class="ark-ui-table-buttons">
          <DefaultButton label='Свернуть все' onClick={() => hideAll(expandedNodeItems.value)}/>
          { props.buttons && props.buttons() }
        </div>
      )
    }

    return () => (
      <>
        { dataLoaderOutsideActive.value
            && <DataLoader />
        }
        { Object.keys(params.value).length === 0
            && count.value === 0
            && !dataLoaderOutsideActive.value
            && !dataLoaderInsideActive.value
            && props.showNodata
            && <NoData
              title={props.title}
              text={props.noDataText ? props.noDataText : 'По вашему запросу ничего не найдено'}
              buttonLabel="Добавить"
              size="S"
              buttonClick={props.noDataButtonClick}
            />
        }

        <div
          ref={rootContainer}
          style={(count.value || !props.showNodata) || Object.keys(params.value).length > 0 || dataLoaderInsideActive.value || props.showNodata ? '' : 'display: none;'}
          class={`ark-ui-table-wrapper ark-ui-tree-table ${isFilterBlock.value ? 'table-no-data' : ''} ${isUseModalFilters.value ? 'with-modal-filters' : ''}`}
        >
          <ARKHeader
            settingsExpose={settings}
            isDisableTop={props.disableTop}
            isShowCount={props.showCount}
            isShowSmallTitle={props.showSmallName}
            isShowSettings={props.showSettings}
            isSmallSettingsButton={props.isSmallSettingsButton}
            buttons={buttons}
            count={setCountSpaces(count.value)}
            columns={columns}
            computedColumns={computedColumns}
            title={props.showSmallName && props.title ? props.title : undefined}
            onUpdateSettings={updateColumnSettings}
          />

          <div class="ark-ui-table-body-wrapper" ref={itemList}>

            <div class="ark-ui-table-body-wrapper-inner">
              <div class="ark-ui-table">
                <div class="ark-ui-table-header">
                  <div class="ark-ui-table-header-row">
                    { props.dragAndDropSorting && (
                      <div class="ark-ui-table-header-cell ark-ui-table-checkbox-cell"/>
                    ) }

                    { props.canCheck !== 'none' && (
                      <div
                        class={`ark-ui-table-header-cell ark-ui-table-checkbox-cell
                          ${hasHorizontalEyesightColumn.value ? 'ark-ui-table-checkbox-fixed' : ''}`}
                        style="width: 0;"
                      >
                        { props.canCheck === 'many' && (
                          <CheckBox
                            class={`${isHeadCheckboxHalfChecked.value
                              ? 'ark-ui-table-multiple-checkbox'
                              : ''}`}
                            modelValue={checkedItems.value.length === expandedNodeItems.value.length}
                            onValueChange={() => checkAll(expandedNodeItems.value, props.onCheckItem, props.canItemBeChecked)} />
                        ) }
                      </div>
                    ) }

                    { props.isExpandRowButton && (
                      <div
                        class="ark-ui-table-header-cell ark-ui-table-expand-cell"
                        style={`left: ${hasHorizontalEyesightColumn.value ? expandRowMargin.value : ''}px;`}
                      />
                    ) }

                    <DefaultHeaders
                      sortField={sortField.value}
                      sortOrder={sortOrder.value}
                      setSort={setSort}
                      columns={computedColumns}
                      smartFilter={props.smartFilter}
                      params={params.value}
                      horizontalEyesightMargins={horizontalEyesightMargins.value}
                      enableSorting={props.enableSorting}
                      tableContainer={rootContainer.value}
                      resizerHeight={itemList.value?.clientHeight}
                    />

                    { props.rowActions && <div class="ark-ui-table-header-cell" style="width: 0;"/> }
                  </div>
                </div>

                { !dataLoaderInsideActive.value
                    && <div
                      style={count.value > 0 ? '' : 'display: none;'}
                      class={'ark-ui-table-body'}
                    >
                      { expandedNodeItems.value.map((item, idx) => (
                        <div
                          class={`ark-ui-table-body-row ${isChecked(item) ? 'is-selected' : ''} ${!props.canItemBeChecked(item) ? 'is-disabled-row' : ''}`}
                          key={item.id}
                          onClick={() => props.onRowClick && props.onRowClick(item)}
                          onContextmenu={(e: MouseEvent) => onContextmenu(e, item)}
                          style={{
                            backgroundColor: props.setRowColor ? props.setRowColor(item, idx) : '',
                            filter: isItemDisabled(item) ? 'opacity(55%)' : '',
                          }}
                        >
                          { props.dragAndDropSorting && (
                            <DragAndDropItem
                              item={item}
                              itemList={itemList}
                              draggedItem={draggedItem}
                              dragAndDropSorting={props.dragAndDropSorting}
                            />
                          ) }

                          { props.canCheck !== 'none' && (
                            <div
                              class={`ark-ui-table-body-cell ark-ui-table-checkbox-cell
                                ${hasHorizontalEyesightColumn.value ? 'ark-ui-table-checkbox-fixed' : ''}`}
                              onClick={(e) => e.stopPropagation()}
                            >
                              {renderElem(props.canCheck, item)}
                            </div>
                          ) }

                          { props.isExpandRowButton && (props.canExpandRow(item) ? (
                            <div
                              class="ark-ui-table-body-cell ark-ui-table-expand-cell"
                              style={`left: ${hasHorizontalEyesightColumn.value ? expandRowMargin.value : ''}px;`}
                              onClick={(e) => { e.stopPropagation() }}
                            >
                              <div
                                class="ark-ui-table-expand-cell-wrapper"
                                onClick={() => { toggleExpandRow(item) }}
                              >
                                <ExpandButton />
                              </div>
                            </div>
                          ) : (
                            <div
                              class="ark-ui-table-body-cell ark-ui-table-expand-cell"
                              style={`left: ${hasHorizontalEyesightColumn.value ? expandRowMargin.value : ''}px;`}
                            >
                              <ExpandButton style='opacity: 0 !important; pointer-events: none !important; user-select: none !important;' />
                            </div>
                          )) }

                          { computedColumns.value.map((column, idx) => (
                            <div
                              key={column.name}
                              class={`
                                ark-ui-table-body-cell
                                ${column.horizontalEyesight ? 'ark-ui-table-eyesight' : ''}
                              `}
                              style={`
                                width: ${column.width}px;
                                min-width: ${column.width}px;
                                max-width: ${column.width}px;
                                overflow: ${column.overflow};
                                --horizontalEyesightMargin: ${horizontalEyesightMargins.value[idx] || 0}px;
                              `}
                            >
                              <div class="ark-ui-table-body-cell-inner" style={idx === 0 && item.depth !== DEFAULT_DEPTH ? `padding-left: ${16 * item.depth}px !important` : ''}>
                                <div class="ark-ui-table-cell-wrapper">
                                  <div>
                                    { !item.isEmpty && idx === 0 && (
                                      <span
                                        class={`ark-ui-table-expand
                                          ${item.isExpanded ? 'is-expanded' : ''}
                                          ${item.isLoading ? 'loading' : ''}`}
                                        onClick={(e) => { expand(item); e.stopPropagation() }}
                                      />
                                    ) }
                                    { item.isEmpty && idx === 0 && (<div style="width: 16px;"/>) }
                                  </div>
                                  <div>
                                    { column.field(item, column, item.isExpandedRow) }
                                  </div>
                                </div>
                              </div>
                            </div>
                          )) }

                          { props.rowActions && (
                            <div class="ark-ui-table-row-actions">
                              { props.rowActions(item) }
                            </div>
                          ) }

                          {props.isUsePagination && item.isLastChildInGroup && (
                            <div class="ark-ui-tree-table-pagination-line">{
                              getTargetState(item.parent)?.isLoadingNextPage
                                ? <LineLoader />
                                : <PaginationTrigger key={`pagination-item-${item.parent?.id || 'main'}`} onDown={() => loadNext(item.parent)} />
                            }</div>
                          )}
                        </div>
                      )) }
                    </div>
                }
              </div>
            </div>

            { dataLoaderInsideActive.value && <DataLoader /> }

            { count.value === 0 && !dataLoaderInsideActive.value && (
              isFiltersSet.value
                ? (
                  <div class='wrap-no-data'>
                    <NoData
                      icon="search"
                      text={props.noDataText ? props.noDataText : 'По вашему запросу ничего не найдено'}
                      size="S"
                    />
                  </div>
                )
                : (
                  <div class='wrap-no-data'>
                    <NoData
                      icon="box"
                      text={props.noDataText ? props.noDataText : 'Нет данных для отображения'}
                      buttonClick={props.noDataButtonClick}
                      buttonLabel="Добавить"
                      size="S"
                    />
                  </div>
                )
            )}

          </div>

          { !props.isHideActionBar && showActionBar.value && (
            <ActionBar
              actions={props.actions}
              text={props.isCounterVisible
                ? `Выбрано: ${setCountSpaces(checkedItems.value.length)} из ${setCountSpaces(count.value)}`
                : ''
              }
              callbackValue={checkedItems.value}
              itemsLength={count.value || undefined}
              onClose={clearChecked}
            />
          ) }

          { props.isHideContextMenu ? '' : (
            <ContextMenu
              ref={contextMenu}
              onClose={() => {
                contextMenuElement.value = undefined
                clickedColumnElement.value = undefined
              }}
            />
          ) }
        </div>
      </>
    )
  },
})
</script>

<style lang="scss">
@import '../styles.scss';
</style>

<style lang="scss" scoped>
.ark-ui-tree-table-pagination-line {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
}

.ark-ui-table {
  &-body {
    &-row {
      position: relative;

      &:hover .ark-ui-table-row-actions {
        opacity: 1;
      }
    }
  }

  &-row-actions {
    position: sticky;
    right: 0;
    display: table-cell;
    vertical-align: middle;
    opacity: 0;
    width: 30px;
    box-sizing: border-box;
    background-color: #F6F6FA;
    box-shadow: -20px 0px 20px 2px rgba(246, 246, 250, .8);
    padding: 0 16px;
  }

}
.wrap-no-data {
  height: 100%;
}
</style>
