<template>
  <div class="incident-timeline" v-if="hasData">
    <!-- Axis labels -->
    <div class="flex justify-between text-xs text-text-dim mb-1 px-1 font-mono">
      <span>{{ startLabel }}</span>
      <span class="text-center text-text-dim opacity-60 text-[10px]">{{ totalLabel }}</span>
      <span>{{ endLabel }}</span>
    </div>

    <!-- SVG strip -->
    <div class="relative rounded border border-surface-border bg-surface overflow-hidden" style="height:64px">
      <svg
        :viewBox="`0 0 ${W} ${H}`"
        preserveAspectRatio="none"
        class="w-full h-full"
        @mousemove="onMouseMove"
        @mouseleave="tooltip = null"
      >
        <!-- Burst density bands (bin shading) -->
        <rect
          v-for="(bin, i) in densityBins"
          :key="`bin-${i}`"
          :x="bin.x"
          :width="bin.w"
          y="0"
          :height="H"
          :fill="bin.fill"
          :fill-opacity="bin.opacity"
        />

        <!-- Gap markers -->
        <line
          v-for="(gap, i) in gapMarkers"
          :key="`gap-${i}`"
          :x1="gap.x"
          :x2="gap.x"
          y1="4"
          :y2="H - 4"
          stroke="var(--color-text-dim)"
          stroke-width="1"
          stroke-dasharray="3,3"
          opacity="0.5"
        />

        <!-- Event ticks -->
        <rect
          v-for="(ev, i) in eventTicks"
          :key="`ev-${i}`"
          :x="ev.x - 1"
          width="2"
          :y="ev.y"
          :height="ev.h"
          :fill="ev.color"
          :fill-opacity="ev.alpha"
          class="cursor-pointer"
          @click="$emit('select-entry', ev.index)"
        />

        <!-- Axis baseline -->
        <line
          x1="0" :x2="W" :y1="H - 6" :y2="H - 6"
          stroke="var(--color-surface-border)"
          stroke-width="1"
        />
      </svg>

      <!-- Hover tooltip -->
      <div
        v-if="tooltip"
        class="absolute pointer-events-none z-10 bg-surface-raised border border-surface-border rounded px-2 py-1 text-xs text-text-primary shadow-md max-w-xs truncate"
        :style="{ left: `${tooltip.px}px`, top: '4px', transform: tooltip.flip ? 'translateX(-100%)' : '' }"
      >
        <span :class="severityClass(tooltip.severity)" class="mr-1 font-bold">{{ tooltip.severity }}</span>
        <span class="text-text-dim mr-1">{{ tooltip.time }}</span>
        <span class="text-text-muted">{{ tooltip.text }}</span>
      </div>
    </div>

    <!-- Legend -->
    <div class="flex gap-3 mt-1.5 text-[10px] text-text-dim px-1">
      <span
        v-for="sev in legendItems"
        :key="sev.label"
        class="flex items-center gap-1"
      >
        <span class="inline-block w-2 h-2 rounded-sm" :style="{ background: sev.color }"></span>
        {{ sev.label }}
      </span>
      <span class="ml-auto">{{ entries.length }} events</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'

interface Entry {
  entry_id: string
  source_id: string
  timestamp_iso: string | null
  severity: string | null
  text: string
}

const props = defineProps<{
  entries: Entry[]
  startedAt?: string | null
  endedAt?: string | null
}>()

defineEmits<{
  'select-entry': [index: number]
}>()

// SVG logical dimensions
const W = 1000
const H = 64

// ── colour map ─────────────────────────────────────────────────────────────
const SEV_COLORS: Record<string, string> = {
  DEBUG:    'var(--color-sev-debug)',
  INFO:     'var(--color-sev-info)',
  WARN:     'var(--color-sev-warn)',
  WARNING:  'var(--color-sev-warn)',
  ERROR:    'var(--color-sev-error)',
  CRITICAL: 'var(--color-sev-critical)',
}

function sevColor(sev: string | null): string {
  return SEV_COLORS[(sev ?? '').toUpperCase()] ?? 'var(--color-text-dim)'
}

// ── time range ──────────────────────────────────────────────────────────────
const timed = computed(() =>
  props.entries
    .filter(e => e.timestamp_iso)
    .map(e => ({ ...e, ms: new Date(e.timestamp_iso!).getTime() }))
    .sort((a, b) => a.ms - b.ms)
)

const tMin = computed(() => {
  if (props.startedAt) return new Date(props.startedAt).getTime()
  return timed.value[0]?.ms ?? Date.now()
})

const tMax = computed(() => {
  if (props.endedAt) return new Date(props.endedAt).getTime()
  const last = timed.value[timed.value.length - 1]?.ms ?? Date.now()
  return Math.max(last, tMin.value + 1000) // at least 1s span
})

const span = computed(() => Math.max(tMax.value - tMin.value, 1))

function xOf(ms: number): number {
  return ((ms - tMin.value) / span.value) * W
}

const hasData = computed(() => timed.value.length > 0)

// ── axis labels ─────────────────────────────────────────────────────────────
function fmtTs(ms: number): string {
  return new Date(ms).toLocaleTimeString(undefined, {
    hour: '2-digit', minute: '2-digit', second: '2-digit',
  })
}

function fmtDuration(ms: number): string {
  const s = Math.round(ms / 1000)
  if (s < 60) return `${s}s`
  if (s < 3600) return `${Math.floor(s / 60)}m ${s % 60}s`
  return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`
}

const startLabel = computed(() => fmtTs(tMin.value))
const endLabel   = computed(() => fmtTs(tMax.value))
const totalLabel = computed(() => fmtDuration(span.value))

// ── density bins (burst shading) ────────────────────────────────────────────
const NUM_BINS = 50

const densityBins = computed(() => {
  const binW = span.value / NUM_BINS
  const counts = new Array<number>(NUM_BINS).fill(0)
  for (const e of timed.value) {
    const idx = Math.min(Math.floor((e.ms - tMin.value) / binW), NUM_BINS - 1)
    counts[idx] = (counts[idx] ?? 0) + 1
  }
  const maxCount = Math.max(...counts, 1)
  return counts.map((count, i) => ({
    x: (i / NUM_BINS) * W,
    w: W / NUM_BINS + 0.5,
    fill: count > 0 ? 'var(--color-accent)' : 'transparent',
    opacity: count > 0 ? Math.min(0.08 + (count / maxCount) * 0.25, 0.33) : 0,
  }))
})

// ── gap markers (silence periods >10% of span or >60s) ─────────────────────
const gapMarkers = computed(() => {
  if (timed.value.length < 2) return []
  const minGapMs = Math.max(span.value * 0.1, 60_000)
  const markers: { x: number }[] = []
  for (let i = 1; i < timed.value.length; i++) {
    const prev = timed.value[i - 1]!
    const curr = timed.value[i]!
    const gap = curr.ms - prev.ms
    if (gap >= minGapMs) {
      markers.push({ x: xOf(prev.ms + gap / 2) })
    }
  }
  return markers
})

// ── event ticks ─────────────────────────────────────────────────────────────
const SEV_HEIGHT: Record<string, number> = {
  DEBUG: 16, INFO: 24, WARN: 32, WARNING: 32, ERROR: 44, CRITICAL: 52,
}

const eventTicks = computed(() =>
  timed.value.map((e, i) => {
    const sevKey = (e.severity ?? '').toUpperCase()
    const h = SEV_HEIGHT[sevKey] ?? 24
    return {
      index: i,
      x: xOf(e.ms),
      y: H - 6 - h,
      h,
      color: sevColor(e.severity),
      alpha: 0.85,
    }
  })
)

// ── legend items (only severities present in this incident) ─────────────────
const legendItems = computed(() => {
  const seen = new Set(timed.value.map(e => (e.severity ?? 'UNKNOWN').toUpperCase()))
  return (['CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG'] as const)
    .filter(s => seen.has(s))
    .map(s => ({ label: s, color: sevColor(s) }))
})

// ── hover tooltip ────────────────────────────────────────────────────────────
interface Tooltip {
  px: number
  flip: boolean
  severity: string | null
  time: string
  text: string
}

const tooltip = ref<Tooltip | null>(null)

function onMouseMove(e: MouseEvent) {
  const svg = e.currentTarget as SVGElement
  const rect = svg.getBoundingClientRect()
  const relX = (e.clientX - rect.left) / rect.width  // 0..1
  const ms = tMin.value + relX * span.value

  // Find nearest entry
  let nearest = timed.value[0]
  let nearestDist = Infinity
  for (const entry of timed.value) {
    const d = Math.abs(entry.ms - ms)
    if (d < nearestDist) { nearestDist = d; nearest = entry }
  }
  if (!nearest) return

  // Only show if within ~3% of span
  if (nearestDist > span.value * 0.03 + 5000) {
    tooltip.value = null
    return
  }

  const px = e.clientX - rect.left
  tooltip.value = {
    px,
    flip: px > rect.width * 0.7,
    severity: nearest.severity,
    time: fmtTs(nearest.ms),
    text: nearest.text.slice(0, 120),
  }
}

function severityClass(sev: string | null): string {
  return {
    ERROR: 'text-sev-error', CRITICAL: 'text-sev-critical',
    WARN: 'text-sev-warn', WARNING: 'text-sev-warn',
    INFO: 'text-sev-info', DEBUG: 'text-text-dim',
  }[(sev ?? '').toUpperCase()] ?? 'text-text-dim'
}
</script>
