<script setup lang="ts">
import type { RunnerTask as Task, TaskState } from 'vitest'
import type { TaskTreeNodeType } from '~/composables/explorer/types'
import { Tooltip as VueTooltip } from 'floating-vue'
import { computed, nextTick } from 'vue'
import { client, config, isReport, runFiles, runTask } from '~/composables/client'
import { showTaskSource } from '~/composables/codemirror'
import { explorerTree } from '~/composables/explorer'
import { hasFailedSnapshot } from '~/composables/explorer/collector'
import { escapeHtml, highlightRegex } from '~/composables/explorer/state'
import { coverageEnabled, disableCoverage } from '~/composables/navigation'
import { getBadgeTextColor } from '~/utils/task'
import IconAction from '../IconAction.vue'
import IconButton from '../IconButton.vue'
import StatusIcon from '../StatusIcon.vue'

// TODO: better handling of "opened" - it means to forcefully open the tree item and set in TasksList right now
const {
  taskId,
  indent,
  name,
  duration,
  slow,
  current,
  opened,
  expandable,
  typecheck,
  label,
  type,
  disableTaskLocation,
  onItemClick,
  projectNameColor,
  state,
} = defineProps<{
  taskId: string
  name: string
  indent: number
  typecheck?: boolean
  label?: string
  duration?: number
  slow?: boolean
  state?: TaskState
  current: boolean
  type: TaskTreeNodeType
  opened: boolean
  expandable: boolean
  search?: string
  projectName?: string
  projectNameColor: string
  disableTaskLocation?: boolean
  onItemClick?: (task: Task) => void
}>()

const task = computed(() => client.state.idMap.get(taskId))

const failedSnapshot = computed(() => {
  // don't traverse the tree if it's a report
  if (isReport) {
    return false
  }

  const t = task.value
  return t && hasFailedSnapshot(t)
})

function toggleOpen() {
  if (!expandable) {
    onItemClick?.(task.value!)
    return
  }

  if (opened) {
    explorerTree.collapseNode(taskId)
  }
  else {
    explorerTree.expandNode(taskId)
  }
}

async function onRun(task: Task) {
  onItemClick?.(task)
  if (coverageEnabled.value) {
    disableCoverage.value = true
    await nextTick()
  }

  if (type === 'file') {
    await runFiles([task.file])
  }
  else {
    await runTask(task)
  }
}

function updateSnapshot(task: Task) {
  return client.rpc.updateSnapshot(task.file)
}

const data = computed(() => {
  return indent <= 0 ? [] : Array.from({ length: indent }, (_, i) => `${taskId}-${i}`)
})
const gridStyles = computed(() => {
  const entries = data.value
  const gridColumns: string[] = []
  // folder icon
  if (type === 'file' || type === 'suite') {
    gridColumns.push('min-content')
  }

  // status icon
  gridColumns.push('min-content')
  // typecheck icon
  if (type === 'suite' && typecheck) {
    gridColumns.push('min-content')
  }
  // text content
  gridColumns.push('minmax(0, 1fr)')
  // action buttons
  gridColumns.push('min-content')

  // all the vertical lines with width 1rem and mx-2: always centered
  return `grid-template-columns: ${
    entries.map(() => '1rem').join(' ')
  } ${gridColumns.join(' ')};`
})

const runButtonTitle = computed(() => {
  if (config.value.api?.allowExec === false) {
    return 'Cannot run tests when `api.allowExec` is `false`. Did you expose UI to the internet?'
  }
  return type === 'file'
    ? 'Run current file'
    : type === 'suite'
      ? 'Run all tests in this suite'
      : 'Run current test'
})

const escapedName = computed(() => escapeHtml(name))
const highlighted = computed(() => {
  const regex = highlightRegex.value
  const useName = escapedName.value
  return regex
    ? useName.replace(regex, match => `<span class="highlight">${match}</span>`)
    : useName
})

const disableShowDetails = computed(() => type !== 'file' && disableTaskLocation)
const showDetailsTooltip = computed(() => {
  return type === 'file'
    ? 'Open test details'
    : type === 'suite'
      ? 'View Suite Source Code'
      : 'View Test Source Code'
})
const showDetailsClasses = computed(() => disableShowDetails.value ? 'color-red5 dark:color-#f43f5e' : null)

function showDetails() {
  const t = task.value!
  if (type === 'file') {
    onItemClick?.(t)
  }
  else {
    showTaskSource(t)
  }
}

const projectNameTextColor = computed(() => getBadgeTextColor(projectNameColor))

/**
experiments trying to show tags compactly
const tagsBorderGradient = computed(() => {
  const t = task.value!
  if (!t || t.type !== 'test' || !t.tags?.length) {
    return null
  }
  const colors = t.tags.map(t => getBadgeNameColor(t)).reverse()
  const percent = 100 / colors.length
  const res = `linear-gradient(to bottom left, ${colors.map(c => `${c} ${percent}%`).join(', ')})`
  return res
})
const tagsBgGradient = computed(() => {
  const t = task.value!
  if (!t || t.type !== 'test' || !t.tags?.length) {
    return null
  }
  const colors = t.tags.map(t => getBadgeNameColor(t, true)).reverse()
  const percent = 100 / colors.length
  const res = `linear-gradient(to bottom left, ${colors.map(c => `${c} ${percent}%`).join(', ')})`
  return res
})
 */
</script>

<template>
  <div
    v-if="task"
    items-center
    p="x-2 y-1"
    grid="~ rows-1 items-center gap-x-2"
    w-full
    h-28px
    border-rounded
    hover="bg-active"
    cursor-pointer
    class="item-wrapper"
    :style="gridStyles"
    :aria-label="name"
    :data-current="current"
    data-testid="explorer-item"
    @click="toggleOpen()"
  >
    <template v-if="indent > 0">
      <div v-for="i in data" :key="i" border="solid gray-500 dark:gray-400" class="vertical-line" h-28px inline-flex mx-2 op20 />
    </template>
    <div v-if="type === 'file' || type === 'suite'" w-4>
      <div :class="opened ? 'i-carbon:chevron-down' : 'i-carbon:chevron-right op20'" op20 />
    </div>
    <StatusIcon :state="state" :mode="task.mode" :failed-snapshot="failedSnapshot" w-4 />
    <div flex items-baseline gap-2 overflow-hidden>
      <div v-if="type === 'file' && typecheck" v-tooltip.bottom="'This is a typecheck test. It won\'t report results of the runtime tests'" class="i-logos:typescript-icon" flex-shrink-0 />
      <span v-if="type === 'file' && label" class="rounded-sm px-1 text-xs font-light bg-cyan-500/20 text-cyan-700 dark:text-cyan-300" flex-shrink-0>{{ label }}</span>
      <span text-sm truncate font-light>
        <span v-if="type === 'file' && projectName" class="rounded-full py-0.5 px-2 mr-1 text-xs" :style="{ backgroundColor: projectNameColor, color: projectNameTextColor }">
          {{ projectName }}
        </span>
        <span :class="state === 'fail' ? 'text-red-700 dark:text-red-500' : undefined" v-html="highlighted" />
      </span>
      <span
        v-if="typeof duration === 'number'"
        text="xs"
        :class="slow ? 'text-yellow-700 dark:text-yellow-500' : 'op20'"
        style="white-space: nowrap"
      >
        {{ duration > 0 ? duration : '< 1' }}ms
      </span>
    </div>
    <div gap-1 justify-end items-center flex-grow-1 pl-1 class="test-actions">
      <!-- <div
        v-if="tagsBorderGradient"
        text-xs
        rounded-full
        flex
        justify-center
        items-center
        class="w-[1.1rem] h-[1.1rem]"
        :style="{
          background: tagsBorderGradient,
        }"
      >
        <div :style="{ background: tagsBgGradient }" class="w-[0.9rem] h-[0.9rem]" rounded-full />
      </div> -->
      <IconAction
        v-if="!isReport && failedSnapshot && config.api?.allowExec && config.api?.allowWrite"
        v-tooltip.bottom="'Fix failed snapshot(s)'"
        data-testid="btn-fix-snapshot"
        title="Fix failed snapshot(s)"
        icon="i-carbon:result-old"
        @click.prevent.stop="updateSnapshot(task)"
      />
      <VueTooltip
        placement="bottom"
        class="w-1.4em h-1.4em op100 rounded flex"
        :class="showDetailsClasses"
      >
        <IconButton
          data-testid="btn-open-details"
          :icon="type === 'file' ? 'i-carbon:intrusion-prevention' : 'i-carbon:code-reference'"
          @click.prevent.stop="showDetails"
        />
        <template #popper>
          <div v-if="disableShowDetails" class="op100 gap-1 p-y-1" grid="~ items-center cols-[1.5em_1fr]">
            <div class="i-carbon:information-square w-1.5em h-1.5em" />
            <div>{{ showDetailsTooltip }}: this feature is not available, you have disabled <span class="text-[#add467]">includeTaskLocation</span> in your configuration file.</div>
            <div style="grid-column: 2">
              Clicking this button the code tab will position the cursor at first line in the source code since the UI doesn't have the information available.
            </div>
          </div>
          <div v-else>
            {{ showDetailsTooltip }}
          </div>
        </template>
      </VueTooltip>
      <IconButton
        v-if="!isReport"
        v-tooltip.bottom="runButtonTitle"
        data-testid="btn-run-test"
        :title="runButtonTitle"
        icon="i-carbon:play-filled-alt"
        text-green-700 dark:text-green-500
        :disabled="config.api?.allowExec === false"
        @click.prevent.stop="onRun(task)"
      />
    </div>
  </div>
</template>

<style scoped>
.vertical-line:first-of-type {
  @apply border-l-2px;
}
.vertical-line + .vertical-line {
  @apply border-r-1px;
}
.test-actions {
  display: none;
}
.item-wrapper:hover .test-actions {
  display: flex;
}
</style>
