<script setup lang="ts">
/**
 * Sparkline — a tiny inline line/area chart with no axes or labels. Perfect for
 * cards, table cells, and stat tiles.
 *
 *   <Sparkline :data="[5,8,6,12,9,14]" />
 *   <Sparkline :data="trend" color="green" area />
 */
import { computed, ref } from 'vue'
import type { RawPoint } from './core/data'
import { normalizeSeries } from './core/data'
import { alpha, resolveColor } from './core/palette'
import { linear } from './core/useScale'
import { useChartAnim } from './core/useChartAnim'
import { chartUid } from './core/uid'

defineOptions({ name: 'BglSparkline' })

const props = withDefaults(defineProps<{
	data: RawPoint[]
	color?: string
	/** Fill the area under the line. */
	area?: boolean
	/** Use a soft gradient for the area fill (default on when `area`). */
	gradient?: boolean
	width?: number
	height?: number
	strokeWidth?: number
	/** Draw a dot on the last point. */
	marker?: boolean
	animated?: boolean
}>(), {
	width: 100,
	height: 28,
	strokeWidth: 2,
	animated: true,
})

const el = ref<HTMLElement>()
const { progress } = useChartAnim({ el, enabled: props.animated, duration: 600 })

const stroke = computed(() => resolveColor(props.color))
const points = computed(() => normalizeSeries(props.data, undefined)[0]?.points ?? [])
const useGradient = computed(() => props.area && props.gradient !== false)
const gradId = chartUid('spark') + '-grad'

const pad = computed(() => props.strokeWidth + (props.marker ? 2 : 0))
const geom = computed(() => {
	const pts = points.value
	if (pts.length < 2) return { line: '', area: '', last: null as null | { x: number; y: number } }
	const ys = pts.map(p => p.y)
	const min = Math.min(...ys)
	const max = Math.max(...ys)
	const sx = linear(0, pts.length - 1, pad.value, props.width - pad.value)
	const sy = linear(min, max, props.height - pad.value, pad.value)
	const coords = pts.map((p, i) => ({ x: sx(i), y: sy(p.y) }))
	const line = coords.map((c, i) => `${i === 0 ? 'M' : 'L'} ${c.x.toFixed(1)} ${c.y.toFixed(1)}`).join(' ')
	const area = `${line} L ${coords[coords.length - 1].x.toFixed(1)} ${props.height - pad.value} L ${coords[0].x.toFixed(1)} ${props.height - pad.value} Z`
	return { line, area, last: coords[coords.length - 1] }
})

// Animate by revealing the stroke via dash offset.
const dashLength = computed(() => props.width * 2)
const dashOffset = computed(() => dashLength.value * (1 - progress.value))
</script>

<template>
	<span ref="el" class="bgl-sparkline inline-block line-height-0 vertical-align-middle" :style="{ width: `${width}px`, height: `${height}px` }">
		<svg :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" preserveAspectRatio="none" class="bgl-sparkline__svg display-block">
			<defs v-if="useGradient">
				<linearGradient :id="gradId" x1="0" y1="0" x2="0" y2="1">
					<stop offset="0%" :stop-color="stroke" stop-opacity="0.35" />
					<stop offset="100%" :stop-color="stroke" stop-opacity="0.02" />
				</linearGradient>
			</defs>
			<path v-if="area && geom.area" :d="geom.area" :fill="useGradient ? `url(#${gradId})` : alpha(stroke, 14)" stroke="none" :opacity="progress" />
			<path
				:d="geom.line" :stroke="stroke" :stroke-width="strokeWidth" fill="none"
				stroke-linecap="round" stroke-linejoin="round"
				:stroke-dasharray="dashLength" :stroke-dashoffset="dashOffset"
			/>
			<circle v-if="marker && geom.last" :cx="geom.last.x" :cy="geom.last.y" :r="strokeWidth + 0.5" :fill="stroke" :opacity="progress" />
		</svg>
	</span>
</template>

<style scoped>
/* Only the SVG overflow has no utility equivalent. */
.bgl-sparkline__svg { overflow: visible; }
</style>
