import {
  endsWith,
  ensureEndingSlash,
  ensureLeadingSlash,
  entries,
  isNumber,
  isPlainObject,
  isString,
  keys,
  startsWith,
  useLocaleConfig,
} from '@vuepress/helper/client'
import type { VNode } from 'vue'
import { computed, defineComponent, h, shallowRef } from 'vue'
import { RouteLink, usePageData, useRoutes, useSiteData } from 'vuepress/client'
import type { CatalogLocaleConfig } from '../../shared/index.js'
import type { CatalogInfo } from '../helpers/index.js'
import { useCatalogInfoGetter } from '../helpers/index.js'

import '../styles/catalog.css'

declare const __CATALOG_LOCALES__: CatalogLocaleConfig

export interface CatalogProps {
  base?: string
  level?: 1 | 2 | 3
}

interface CatalogData extends CatalogInfo {
  level: number
  base: string
  path: string
  children?: CatalogData[]
}

export default defineComponent({
  name: 'Catalog',

  props: {
    /**
     * Catalog Base
     *
     * 目录的基础路径
     *
     * @default current route base
     */
    base: {
      type: String,
      default: '',
    },

    /**
     * Max level of catalog
     *
     * @description only 1,2,3 are supported
     *
     * Catalog 的最大层级
     *
     * @description 目前仅支持 1,2,3
     *
     * @default 3
     */
    level: {
      type: Number,
      default: 3,
    },

    /**
     * Whether show index for catalog
     *
     * 目录是否显示索引
     */
    index: Boolean,

    /**
     * Whether hide `Category` title
     *
     * 是否隐藏 `目录` 标题
     *
     * @default false
     */
    hideHeading: Boolean,
  },

  setup(props) {
    const catalogInfoGetter = useCatalogInfoGetter()
    const locale = useLocaleConfig(__CATALOG_LOCALES__)
    const page = usePageData()
    const routes = useRoutes()
    const siteData = useSiteData()

    const getCatalogData = (): CatalogData[] =>
      entries(routes.value)
        .map(([path, { meta }]) => {
          const info = catalogInfoGetter(meta)

          if (!info) return null

          const level = path.split('/').length

          return {
            level: endsWith(path, '/') ? level - 2 : level - 1,
            base: path.replace(/\/[^/]+\/?$/, '/'),
            path,
            ...info,
          }
        })
        .filter(
          (item): item is CatalogData =>
            isPlainObject(item) && isString(item.title),
        )

    const catalogInfo = shallowRef(getCatalogData())

    const catalogData = computed(() => {
      const base = props.base
        ? ensureLeadingSlash(ensureEndingSlash(props.base))
        : page.value.path.replace(/\/[^/]+$/, '/')
      const baseDepth = base.split('/').length - 2
      const result: CatalogData[] = []

      catalogInfo.value
        .filter(({ level, path }) => {
          // filter those under current base
          if (!startsWith(path, base) || path === base) return false

          if (base === '/') {
            const otherLocales = keys(siteData.value.locales).filter(
              (item) => item !== '/',
            )

            // exclude 404 page and other locales
            if (
              path === '/404.html' ||
              otherLocales.some((localePath) => startsWith(path, localePath))
            )
              return false
          }

          return (
            // level is less than or equal to max level
            level - baseDepth <= props.level
          )
        })
        .sort(
          (
            { title: titleA, level: levelA, order: orderA },
            { title: titleB, level: levelB, order: orderB },
          ) => {
            const level = levelA - levelB

            if (level) return level

            // infoA order is absent
            if (!isNumber(orderA)) {
              // infoB order is absent
              if (!isNumber(orderB))
                // compare title
                return titleA.localeCompare(titleB)

              // infoB order is present
              return orderB
            }

            // infoB order is absent
            if (!isNumber(orderB)) return orderA

            // now we are sure both order exist

            // infoA order is positive
            if (orderA > 0) {
              if (orderB > 0) return orderA - orderB

              return -1
            }

            // both order are negative
            if (orderB < 0) return orderA - orderB

            return 1
          },
        )
        .forEach((info) => {
          const { base, level } = info

          switch (level - baseDepth) {
            case 1: {
              result.push(info)
              break
            }

            case 2: {
              const parent = result.find((item) => item.path === base)

              if (parent) (parent.children ??= []).push(info)
              break
            }

            default: {
              const grandParent = result.find(
                (item) => item.path === base.replace(/\/[^/]+\/$/, '/'),
              )

              if (grandParent) {
                const parent = grandParent.children?.find(
                  (item) => item.path === base,
                )

                if (parent) (parent.children ??= []).push(info)
              }
            }
          }
        })

      return result
    })

    return (): VNode => {
      const isDeep = catalogData.value.some((item) => item.children)

      return h(
        'div',
        { class: ['vp-catalog-wrapper', { index: props.index }] },
        [
          props.hideHeading
            ? null
            : h('h2', { class: 'vp-catalog-main-title' }, locale.value.title),
          catalogData.value.length
            ? h(
                props.index ? 'ol' : 'ul',
                { class: ['vp-catalogs', { deep: isDeep }] },
                catalogData.value.map(
                  ({ children = [], title, path, content }) => {
                    const childLink = h(
                      RouteLink,
                      { class: 'vp-catalog-title', to: path },
                      () => (content ? h(content) : title),
                    )

                    return h(
                      'li',
                      { class: 'vp-catalog' },
                      isDeep
                        ? [
                            h(
                              'h3',
                              {
                                id: title,
                                class: [
                                  'vp-catalog-child-title',
                                  { 'has-children': children.length },
                                ],
                              },
                              [
                                h(
                                  'a',
                                  {
                                    'href': `#${title}`,
                                    'class': 'vp-catalog-header-anchor',
                                    'aria-hidden': true,
                                  },
                                  '#',
                                ),
                                childLink,
                              ],
                            ),
                            children.length
                              ? h(
                                  props.index ? 'ol' : 'ul',
                                  { class: 'vp-child-catalogs' },
                                  children.map(
                                    ({ children = [], content, path, title }) =>
                                      h('li', { class: 'vp-child-catalog' }, [
                                        h(
                                          'div',
                                          {
                                            class: [
                                              'vp-catalog-sub-title',
                                              {
                                                'has-children': children.length,
                                              },
                                            ],
                                          },
                                          [
                                            h(
                                              'a',
                                              {
                                                href: `#${title}`,
                                                class:
                                                  'vp-catalog-header-anchor',
                                              },
                                              '#',
                                            ),
                                            h(
                                              RouteLink,
                                              {
                                                class: 'vp-catalog-title',
                                                to: path,
                                              },
                                              () =>
                                                content ? h(content) : title,
                                            ),
                                          ],
                                        ),
                                        children.length
                                          ? h(
                                              props.index ? 'ol' : 'div',
                                              {
                                                class: props.index
                                                  ? 'vp-sub-catalogs'
                                                  : 'vp-sub-catalogs-wrapper',
                                              },
                                              children.map(
                                                ({ content, path, title }) =>
                                                  props.index
                                                    ? h(
                                                        'li',
                                                        {
                                                          class:
                                                            'vp-sub-catalog',
                                                        },
                                                        h(
                                                          RouteLink,
                                                          { to: path },
                                                          () =>
                                                            content
                                                              ? h(content)
                                                              : title,
                                                        ),
                                                      )
                                                    : h(
                                                        RouteLink,
                                                        {
                                                          class:
                                                            'vp-sub-catalog-link',
                                                          to: path,
                                                        },
                                                        () =>
                                                          content
                                                            ? h(content)
                                                            : title,
                                                      ),
                                              ),
                                            )
                                          : null,
                                      ]),
                                  ),
                                )
                              : null,
                          ]
                        : h(
                            'div',
                            { class: 'vp-catalog-child-title' },
                            childLink,
                          ),
                    )
                  },
                ),
              )
            : h('p', { class: 'vp-empty-catalog' }, locale.value.empty),
        ],
      )
    }
  },
})
