<script setup lang="ts">
/**
 * Donut — donut or pie chart with optional center label. Theme-driven slices.
 *
 *   <Donut :data="[{label:'Direct',value:60},{label:'Referral',value:40}]" />
 *   <Donut :data="segments" :thickness="0.5" center-label="Total" :center-value="100" />
 *   <Donut :data="segments" pie :legend="false" />
 *   <Donut :data="segments" labels="both" />            // value + % on the slices
 *   <Donut :data="segments" pie labels="full" />        // name + value + % stacked
 *   <Donut :data="segments" :legend-value="false" />    // legend shows only %
 */
import { computed, ref } from 'vue'
import { useI18n } from '@bagelink/vue'
import { ramp, resolveColor } from './core/palette'
import { formatValue, type ValueFormat } from './core/format'
import { useChartAnim } from './core/useChartAnim'
import ChartTooltip from './ChartTooltip.vue'
import type { ThemeType } from '../../types'

defineOptions({ name: 'BglDonut' })

interface Slice { label: string; value: number; color?: string }

const props = withDefaults(defineProps<{
	data: Slice[]
	size?: number
	/** Ring thickness as a fraction of radius (0–1). Ignored when `pie`. */
	thickness?: number
	pie?: boolean
	legend?: boolean
	/** Show the formatted amount column in the legend. Default true. */
	legendValue?: boolean
	/** Show the percentage column in the legend. Default true. */
	legendPct?: boolean
	/** Base tone for the slices. Drives the monochrome ramp in `shades` mode. */
	color?: ThemeType | string
	/** Coloring: `shades` = one tone, lightest→darkest (default, looks premium);
	    `multi` = a different tone per slice. Per-slice `color` always wins. */
	mode?: 'shades' | 'multi'
	centerLabel?: string
	centerValue?: number | string
	/** Auto-fill center with the total when no centerValue given. */
	showTotal?: boolean
	currency?: string
	/** On-slice labels. `false` (default) keeps the chart clean.
	    `pct` = %, `value` = amount, `both` = value + %, `full` = name + value + %.
	    Each piece is stacked: name (small), value (large), pct (smaller).
	    Tiny slices are skipped. */
	labels?: false | 'pct' | 'value' | 'both' | 'full'
	/** Hide on-slice labels for slices below this share (0–1). Default 0.05. */
	labelMinShare?: number
	animated?: boolean
}>(), {
	size: 180,
	thickness: 0.38,
	legend: true,
	legendValue: true,
	legendPct: true,
	color: 'primary',
	mode: 'shades',
	showTotal: false,
	labels: false,
	labelMinShare: 0.05,
	animated: true,
})

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

const fmt = computed<ValueFormat>(() => ({ currency: props.currency, compact: true }))
/** Formatted amount for a slice (used on-chart, in the legend + tooltip). */
function fmtValue(v: number): string {
	return formatValue(v, { currency: props.currency, compact: true })
}
const total = computed(() => props.data.reduce((s, d) => s + Math.max(0, d.value), 0) || 1)
const radius = computed(() => props.size / 2)
const inner = computed(() => (props.pie ? 0 : radius.value * (1 - props.thickness)))

interface Arc {
	d: string; color: string; label: string; value: number; pct: number
	/** Centroid for on-slice label placement. */
	lx: number; ly: number
	/** Whether this slice shows an on-chart label at all. */
	tagged: boolean
	/** Stacked on-slice label pieces (empty string = that line is hidden). */
	tagName: string; tagValue: string; tagPct: string
}
const arcs = computed<Arc[]>(() => {
	const r = radius.value
	const ri = inner.value
	const cx = r
	const cy = r
	// Radius where the label centroid sits — pushed toward the outer edge
	// (68% of the way from the inner edge to the rim) so labels sit near the rim.
	const rMid = ri + (r - ri) * 0.68
	let angle = -Math.PI / 2 // start at top
	return props.data.map((d, i) => {
		const frac = Math.max(0, d.value) / total.value
		const sweep = frac * Math.PI * 2 * progress.value
		const a0 = angle
		const a1 = angle + sweep
		angle = a1
		const aMid = (a0 + a1) / 2
		const large = sweep > Math.PI ? 1 : 0
		const x0 = cx + r * Math.cos(a0)
		const y0 = cy + r * Math.sin(a0)
		const x1 = cx + r * Math.cos(a1)
		const y1 = cy + r * Math.sin(a1)
		let d2: string
		if (ri > 0) {
			const xi1 = cx + ri * Math.cos(a1)
			const yi1 = cy + ri * Math.sin(a1)
			const xi0 = cx + ri * Math.cos(a0)
			const yi0 = cy + ri * Math.sin(a0)
			d2 = `M ${x0} ${y0} A ${r} ${r} 0 ${large} 1 ${x1} ${y1} L ${xi1} ${yi1} A ${ri} ${ri} 0 ${large} 0 ${xi0} ${yi0} Z`
		} else {
			d2 = `M ${cx} ${cy} L ${x0} ${y0} A ${r} ${r} 0 ${large} 1 ${x1} ${y1} Z`
		}
		const sliceColor = d.color
			? resolveColor(d.color)
			: props.mode === 'multi'
				? resolveColor(undefined, i)
				: ramp(props.color, props.data.length, i)
		const pct = frac * 100
		const tagged = !!props.labels && frac >= props.labelMinShare
		return {
			d: d2, color: sliceColor, label: d.label, value: d.value, pct,
			lx: cx + rMid * Math.cos(aMid),
			ly: cy + rMid * Math.sin(aMid),
			tagged,
			tagName: tagged && props.labels === 'full' ? d.label : '',
			tagValue: tagged && props.labels !== 'pct' ? fmtValue(d.value) : '',
			tagPct: tagged && props.labels !== 'value' ? `${pct.toFixed(0)}%` : '',
		}
	})
})

const centerText = computed(() => {
	if (props.centerValue !== undefined) return String(props.centerValue)
	if (props.showTotal) return formatValue(total.value, fmt.value)
	return ''
})

// ── Shared hover tooltip (slice-based, mouse-follow) ─────────────────────────
const hoverIdx = ref<number | null>(null)
const tipX = ref(0)
const tipY = ref(0)
function onSliceEnter(i: number) { hoverIdx.value = i }
function onLeave() { hoverIdx.value = null }
function onMove(e: MouseEvent) {
	const host = (e.currentTarget as HTMLElement).getBoundingClientRect()
	tipX.value = e.clientX - host.left
	tipY.value = e.clientY - host.top
}
const hoverArc = computed(() => (hoverIdx.value == null ? null : arcs.value[hoverIdx.value]))
const hoverValue = computed(() => (hoverArc.value ? fmtValue(hoverArc.value.value) : ''))
const hoverStats = computed(() => (hoverArc.value ? [{ label: $t('charts.share'), value: `${hoverArc.value.pct.toFixed(0)}%` }] : []))
const tooltipStyle = computed(() => ({ left: `${tipX.value}px`, top: `${tipY.value}px` }))
</script>

<template>
	<div
		ref="el" class="bgl-donut w-100p inline-flex gap-1"
		:class="legend ? 'row flex-wrap justify-content-center' : 'column'"
	>
		<div
			class="bgl-donut__chart relative flex-shrink-0" :style="{ width: `${size}px`, height: `${size}px` }"
			@mousemove="onMove" @mouseleave="onLeave"
		>
			<svg :width="size" :height="size" :viewBox="`0 0 ${size} ${size}`">
				<path
					v-for="(a, i) in arcs" :key="i"
					:d="a.d" :fill="a.color" class="bgl-donut__slice cursor-pointer"
					:class="{ 'opacity-5': hoverIdx != null && hoverIdx !== i }"
					@mouseenter="onSliceEnter(i)"
				/>
				<text
					v-for="(a, i) in arcs" v-show="a.tagged" :key="`t${i}`"
					:x="a.lx" :y="a.ly" class="bgl-donut__tag pointer-events-none"
					text-anchor="middle" dominant-baseline="central"
				>
					<tspan v-if="a.tagName" :x="a.lx" dy="-0.7em" class="bgl-donut__tag-name opacity-9 txt-medium txt-0625rem">{{ a.tagName }}</tspan>
					<tspan v-if="a.tagValue" :x="a.lx" :dy="a.tagName ? '1.15em' : '0'" class="bgl-donut__tag-val txt-bold txt-0875rem">{{ a.tagValue }}</tspan>
					<tspan v-if="a.tagPct" :x="a.lx" :dy="(a.tagName || a.tagValue) ? '1.5em' : '0'" class="bgl-donut__tag-pct opacity-9 txt-05rem">{{ a.tagPct }}</tspan>
				</text>
			</svg>
			<div v-if="!pie && (centerText || centerLabel)" class="absolute-fill flex-center column pointer-events-none gap-025">
				<span v-if="centerText" class="semibold txt20 line-height-1">{{ centerText }}</span>
				<span v-if="centerLabel" class="color-gray txt12">{{ centerLabel }}</span>
			</div>
			<ChartTooltip
				v-if="hoverArc" above :style="tooltipStyle"
				:label="hoverArc.label" :hero="hoverValue" :stats="hoverStats"
			/>
		</div>

		<ul v-if="legend" class="bgl-donut__legend grid gap-025">
			<li v-for="(a, i) in arcs" :key="i" class="flex gap-05 txt13">
				<span class="bgl-donut__dot flex-shrink-0" :style="{ background: a.color }" />
				<span class="bgl-donut__legname ms-0 me-auto my-0 p-0">{{ a.label }}</span>
				<span v-if="legendValue" class="semibold tabular-nums">{{ fmtValue(a.value) }}</span>
				<span v-if="legendPct" class="bgl-donut__legpct color-gray tabular-nums txt-end">{{ a.pct.toFixed(0) }}%</span>
			</li>
		</ul>
	</div>
</template>

<style scoped>
.bgl-donut__slice { transition: opacity 0.15s ease; outline: none; }
.bgl-donut__slice:focus,
.bgl-donut__slice:focus-visible { outline: none; }
.bgl-donut__tag {
	fill: #fff; font-variant-numeric: tabular-nums;
	paint-order: stroke; stroke: rgba(0, 0, 0, 0.28); stroke-width: 2.5px;
	stroke-linejoin: round;
}
.bgl-donut__legend { list-style: none; min-width: 0; }
.bgl-donut__dot { width: 10px; height: 10px; border-radius: 3px; }
.bgl-donut__legpct { min-width: 2.5em; }
</style>
