import type { RunnerTestFile as File, RunnerTask as Task } from 'vitest'
import type { FileTreeNode, Filter, FilteredTests, SortUIType, TreeFilterState, UITaskTreeNode } from './types'
import { useLocalStorage } from '@vueuse/core'
import { computed, reactive, ref, shallowRef } from 'vue'
import { availableProjects } from '~/composables/client'
import { caseInsensitiveMatch } from '~/utils/task'
// importing from the source because we need to bundle it
import { createTagsFilter } from '../../../../vitest/src/runtime/runner/utils/tags'
import { config } from '../client'
import { explorerTree } from './index'

export const uiFiles = shallowRef<FileTreeNode[]>([])
export const uiEntries = shallowRef<UITaskTreeNode[]>([])
export const openedTreeItems = useLocalStorage<string[]>(
  'vitest-ui_task-tree-opened',
  [],
  { shallow: true },
)
export const ALL_PROJECTS = '__vitest_ui_all_projects__'
export const openedTreeItemsSet = computed(() => new Set(openedTreeItems.value))
export const treeFilter = useLocalStorage<TreeFilterState>(
  'vitest-ui_task-tree-filter',
  {
    expandAll: undefined,
    failed: false,
    success: false,
    skipped: false,
    slow: false,
    onlyTests: false,
    search: '',
    project: ALL_PROJECTS,
    projectSort: undefined,
  },
)
export const projectSort = ref<SortUIType>(treeFilter.value.projectSort || 'default')
export const currentProject = shallowRef(treeFilter.value?.project || ALL_PROJECTS)
export const enableProjects = computed(() => availableProjects.value.length > 1)
export const disableClearProjects = computed(() => currentProject.value === ALL_PROJECTS)
export const currentProjectName = computed(() => {
  return !enableProjects.value || currentProject.value === ALL_PROJECTS ? undefined : currentProject.value
})
export const search = ref<string>(treeFilter.value.search)
const tagExpressionsCache = new Map<string, { error?: string; matcher: (tags: string[]) => boolean }>()
export const searchMatcher = computed(() => {
  if (search.value.startsWith('tag:')) {
    if (!config.value.tags) { // config is not loaded yet
      return { matcher: () => true }
    }
    const tagQuery = search.value.slice(4).trim()
    let filter = tagExpressionsCache.get(tagQuery)
    if (!filter) {
      filter = createSafeFilter(tagQuery)
      tagExpressionsCache.set(tagQuery, filter)
    }
    return {
      matcher: (task: Task) => filter.matcher(task.tags || []),
      error: filter.error,
    }
  }
  return {
    matcher: (task: Task) => search.value === '' || caseInsensitiveMatch(task.name, search.value),
  }
})

function createSafeFilter(
  query: string,
) {
  if (!query) {
    return { matcher: () => true }
  }
  try {
    return { matcher: createTagsFilter([query], config.value.tags || []) }
  }
  catch (error: any) {
    return { matcher: () => false, error: error.message }
  }
}
const htmlEntities: Record<string, string> = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  '\'': '&#39;',
}
export function escapeHtml(str: string) {
  return str.replace(/[&<>"']/g, m => htmlEntities[m])
}
export const highlightRegex = computed(() => {
  const searchString = search.value.toLowerCase()
  return searchString.length ? new RegExp(`(${escapeHtml(searchString)})`, 'gi') : null
})
export const isFiltered = computed(() => search.value.trim() !== '')
export const filter = reactive<Filter>({
  failed: treeFilter.value.failed,
  success: treeFilter.value.success,
  skipped: treeFilter.value.skipped,
  slow: treeFilter.value.slow,
  onlyTests: treeFilter.value.onlyTests,
})
export const isFilteredByStatus = computed(() => {
  if (filter.failed) {
    return true
  }

  if (filter.success) {
    return true
  }

  if (filter.skipped) {
    return true
  }

  if (filter.slow) {
    return true
  }

  return false
})
export const filteredFiles = shallowRef<File[]>([])
export const initialized = ref(false)
export const shouldShowExpandAll = computed(() => {
  const expandAll = treeFilter.value.expandAll
  const opened = openedTreeItems.value

  if (opened.length > 0) {
    return expandAll !== true
  }

  return expandAll !== false
})
export const testsTotal = computed<FilteredTests>(() => {
  const filtered = isFiltered.value
  const filteredByStatus = isFilteredByStatus.value
  const project = currentProjectName.value
  const onlyTests = filter.onlyTests
  const failed = explorerTree.summary.filesFailed
  const success = explorerTree.summary.filesSuccess
  const skipped = explorerTree.summary.filesSkipped
  const running = explorerTree.summary.filesRunning
  const files = filteredFiles.value
  return explorerTree.collectTestsTotal(
    filtered || filteredByStatus || !!project,
    onlyTests,
    files,
    {
      failed,
      success,
      skipped,
      running,
    },
  )
})
