<script setup lang="ts">
/**
 * Funnel — conversion stages. Two looks:
 *  - `flow` (default): one continuous tapering silhouette, segments connected by
 *    smooth curved sides — feels natural for a conversion flow.
 *  - `:flow="false"`: discrete centered stage bars.
 * Hover any segment for value + % from previous + % of top.
 *
 *   <Funnel :data="[{label:'Visitors',value:8200},{label:'Signups',value:3100},…]" />
 *   <Funnel :data="stages" color="purple" :flow="false" />
 *   <Funnel :data="stages" orientation="horizontal" />
 *   <Funnel :stages="['Visitors',…]" :series="[{name:'Organic',data:[…]},…]" />
 */
import { computed, ref } from 'vue'
import { ramp, resolveColor } from './core/palette'
import { formatValue, type ValueFormat } from './core/format'
import { chartUid } from './core/uid'
import ChartTooltip from './ChartTooltip.vue'
import { useChartAnim } from './core/useChartAnim'
import type { ThemeType } from '../../types'

defineOptions({ name: 'BglFunnel' })

interface Stage { label: string; value: number; color?: ThemeType | string }
interface SourceSeries { name: string; color?: ThemeType | string; data: number[] }

const props = withDefaults(defineProps<{
	/** Simple funnel: stages with a single value each. */
	data?: Stage[]
	/** Segmented funnel: stage labels + one series per source. Each series'
	    `data[i]` is that source's count at stage i. Splits each band by source. */
	stages?: (string | number)[]
	series?: SourceSeries[]
	/** Continuous flowing silhouette (default). `false` = discrete bars. */
	flow?: boolean
	/** `vertical` (default): stages stack top→bottom, tapering in width.
	    `horizontal`: stages run left→right, tapering in height. */
	orientation?: 'vertical' | 'horizontal'
	/** Base tone; stages shade from strong → light down the funnel. */
	color?: ThemeType | string
	/** One tone per stage instead of shades. Per-stage `color` wins. */
	mode?: 'shades' | 'multi'
	/** Soft gradient on each segment. Default on. */
	gradient?: boolean
	/** Height of each stage band (px). */
	bandHeight?: number
	fromPrev?: boolean
	fromTop?: boolean
	currency?: string
	prefix?: string
	suffix?: string
	animated?: boolean
}>(), {
	flow: true,
	orientation: 'vertical',
	color: 'primary',
	mode: 'shades',
	gradient: true,
	bandHeight: 52,
	fromPrev: true,
	fromTop: true,
	animated: true,
})

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

const fmt = computed<ValueFormat>(() => ({ currency: props.currency, prefix: props.prefix, suffix: props.suffix }))
const uid = chartUid('funnel')

const W = 100 // viewBox width units
const VPAD = 1 // min cross-axis padding so the narrowest band stays visible

const isSegmented = computed(() => !!props.series?.length)

// Source legend (segmented mode): name + resolved tone per source.
const sources = computed(() =>
	(props.series ?? []).map((s, i) => ({ name: s.name, color: resolveColor(s.color, i) })),
)

// Normalized stage list ({ label, value }) from either `data` or `stages`+`series`.
const stageData = computed<Stage[]>(() => {
	if (isSegmented.value) {
		const labels = props.stages ?? []
		return labels.map((label, i) => ({
			label: String(label),
			value: (props.series ?? []).reduce((sum, s) => sum + (s.data[i] ?? 0), 0),
		}))
	}
	return props.data ?? []
})

const stages = computed(() => {
	const top = stageData.value[0]?.value || 1
	return stageData.value.map((d, i) => {
		const prev = stageData.value[i - 1]?.value
		const base = d.color
			? resolveColor(d.color)
			: props.mode === 'multi'
				? resolveColor(undefined, i)
				: ramp(props.color, stageData.value.length, i)
		const fromTop = (d.value / top) * 100
		const fromPrev = prev ? (d.value / prev) * 100 : 100
		const isFirst = i === 0
		const tipRows = [`<b>${formatValue(d.value, fmt.value)}</b>`]
		if (!isFirst && props.fromTop) tipRows.push(`${fromTop.toFixed(1)}% of top`)
		if (!isFirst && props.fromPrev) tipRows.push(`${fromPrev.toFixed(1)}% from previous`)
		// Segmented: append a per-source breakdown to the tooltip.
		if (isSegmented.value) {
			(props.series ?? []).forEach((s) => {
				const v = s.data[i] ?? 0
				if (v > 0) tipRows.push(`<span style="opacity:.7">${s.name}:</span> ${formatValue(v, fmt.value)}`)
			})
		}
		// Structured per-source rows for the shared tooltip (segmented mode).
		const rows = isSegmented.value
			? (props.series ?? []).map((s, si) => ({
					name: s.name,
					color: sources.value[si].color,
					value: formatValue(s.data[i] ?? 0, fmt.value),
					raw: s.data[i] ?? 0,
				})).filter(r => r.raw > 0)
			: [{ name: d.label, color: base, value: formatValue(d.value, fmt.value), raw: d.value }]
		return {
			label: d.label,
			value: d.value,
			display: formatValue(d.value, fmt.value),
			frac: d.value / top, // 0–1 of the widest
			fromTop,
			fromPrev,
			isFirst,
			color: base,
			rows,
			tip: `<div style="text-align:center">${d.label}<br>${tipRows.join('<br>')}</div>`,
			barFill: props.gradient ? `linear-gradient(90deg, color-mix(in srgb, ${base} 82%, black), ${base})` : base,
		}
	})
})

// ── Flow geometry: connected curved trapezoids ───────────────────────────────
const horizontal = computed(() => props.orientation === 'horizontal')

// Main axis = the direction stages advance; cross axis = the tapering dimension.
// Vertical:   main = svg height, cross = W (width).
// Horizontal: main = svg width,  cross = W (height), drawn left→right.
const mainLen = computed(() => stages.value.length * props.bandHeight)
const svgW = computed(() => (horizontal.value ? mainLen.value : W))
const svgH = computed(() => (horizontal.value ? W : mainLen.value))

// Build a point in (mainPos along axis, crossOffset from center) → "x y".
function pt(main: number, cross: number): string {
	const c = W / 2 + cross
	return horizontal.value ? `${main} ${c}` : `${c} ${main}`
}

// Half cross-extent (viewBox units) for a fraction of the widest stage.
function halfW(frac: number): number {
	const maxHalf = W / 2 - VPAD
	return maxHalf * frac * progress.value + 0.4
}

// Curved tapering band between two cross half-widths along the main axis.
// `a`=start half-width, `b`=end half-width, offset shifts the cross center
// (used for stacked source sub-bands).
function bandPath(m0: number, m1: number, aL: number, aR: number, bL: number, bR: number): string {
	const mm = (m0 + m1) / 2
	return `M ${pt(m0, aL)} L ${pt(m0, aR)} `
		+ `C ${pt(mm, aR)} ${pt(mm, bR)} ${pt(m1, bR)} `
		+ `L ${pt(m1, bL)} `
		+ `C ${pt(mm, bL)} ${pt(mm, aL)} ${pt(m0, aL)} Z`
}

const segments = computed(() => {
	const bh = props.bandHeight
	return stages.value.map((s, i) => {
		const next = stages.value[i + 1] ?? s
		const m0 = i * bh
		const m1 = (i + 1) * bh
		const a = halfW(s.frac)
		const b = halfW(next.frac)
		return {
			...s,
			path: bandPath(m0, m1, -a, a, -b, b),
			gradId: `${uid}-${i}`,
		}
	})
})

// ── Segmented geometry: each band split by source along the cross axis ────────
const subSegments = computed(() => {
	if (!isSegmented.value) return []
	const bh = props.bandHeight
	const series = props.series ?? []
	const out: { path: string; color: string; key: string }[] = []
	stages.value.forEach((s, i) => {
		const isLast = i === stages.value.length - 1
		const next = stages.value[i + 1] ?? s
		const m0 = i * bh
		const m1 = (i + 1) * bh
		const totA = s.value || 1
		const totB = next.value || 1
		const aL = -halfW(s.frac)
		const widthA = halfW(s.frac) * 2
		const bL = -halfW(next.frac)
		const widthB = halfW(next.frac) * 2
		let accA = 0
		let accB = 0
		series.forEach((src, si) => {
			const shareA = (src.data[i] ?? 0) / totA
			// Last band has no "next" — keep its bottom equal to its top.
			const shareB = isLast ? shareA : (src.data[i + 1] ?? 0) / totB
			const a0 = aL + accA * widthA
			const a1 = aL + (accA + shareA) * widthA
			const b0 = bL + accB * widthB
			const b1 = bL + (accB + shareB) * widthB
			accA += shareA
			accB += shareB
			out.push({ path: bandPath(m0, m1, a0, a1, b0, b1), color: sources.value[si].color, key: `${i}-${si}` })
		})
	})
	return out
})

// ── Shared hover tooltip (matches LineChart) ─────────────────────────────────
const hoverIdx = ref<number | null>(null)
const tipX = ref(0)
const tipY = ref(0)

function onSegEnter(i: number) { hoverIdx.value = i }
function onSegLeave() { hoverIdx.value = null }
function onFlowMove(e: MouseEvent) {
	const host = (e.currentTarget as HTMLElement).getBoundingClientRect()
	tipX.value = e.clientX - host.left
	tipY.value = e.clientY - host.top
}

const hoverStage = computed(() => (hoverIdx.value == null ? null : stages.value[hoverIdx.value]))
const hoverStats = computed(() => {
	const s = hoverStage.value
	if (!s) return [] as { label: string; value: string }[]
	const out: { label: string; value: string }[] = []
	if (!s.isFirst && props.fromPrev) out.push({ label: 'From previous', value: `${s.fromPrev.toFixed(1)}%` })
	if (!s.isFirst && props.fromTop) out.push({ label: 'Of top', value: `${s.fromTop.toFixed(1)}%` })
	return out
})
const hoverTotal = computed(() => {
	const s = hoverStage.value
	return s && isSegmented.value ? s.display : null
})
const tooltipStyle = computed(() => ({ left: `${tipX.value}px`, top: `${tipY.value}px` }))
</script>

<template>
	<div ref="el" class="bgl-funnel w-100p relative" :class="[{ 'bgl-funnel--bars': !flow }, flow ? `bgl-funnel--${orientation}` : '']">
		<!-- Flowing silhouette + axis labels -->
		<div v-if="flow" class="bgl-funnel__flow relative" @mousemove="onFlowMove" @mouseleave="onSegLeave">
			<svg
				class="bgl-funnel__svg display-block" :viewBox="`0 0 ${svgW} ${svgH}`"
				:style="horizontal ? { height: `${bandHeight * 3}px` } : { height: `${svgH}px` }"
				preserveAspectRatio="none"
			>
				<defs v-if="gradient && !isSegmented">
					<linearGradient
						v-for="(s, i) in segments" :id="s.gradId" :key="i"
						:x1="0" :y1="0" :x2="horizontal ? 0 : 1" :y2="horizontal ? 1 : 0"
					>
						<stop offset="0%" :stop-color="`color-mix(in srgb, ${s.color} 78%, black)`" />
						<stop offset="100%" :stop-color="s.color" />
					</linearGradient>
				</defs>

				<!-- Simple: one band per stage -->
				<template v-if="!isSegmented">
					<path
						v-for="(s, i) in segments" :key="i"
						:d="s.path" :fill="gradient ? `url(#${s.gradId})` : s.color"
						class="bgl-funnel__seg" :class="{ 'is-dim opacity-5': hoverIdx != null && hoverIdx !== i }"
						@mouseenter="onSegEnter(i)"
					/>
				</template>

				<!-- Segmented: source sub-paths + a transparent band hit-area -->
				<template v-else>
					<path v-for="sub in subSegments" :key="sub.key" :d="sub.path" :fill="sub.color" class="bgl-funnel__sub" />
					<path
						v-for="(s, i) in segments" :key="`hit${i}`"
						:d="s.path" fill="transparent" class="bgl-funnel__hit"
						@mouseenter="onSegEnter(i)"
					/>
				</template>
			</svg>

			<!-- Shared tooltip -->
			<ChartTooltip
				v-if="hoverStage" above :style="tooltipStyle"
				:label="hoverStage.label"
				:hero="isSegmented ? undefined : hoverStage.display"
				:rows="isSegmented ? hoverStage.rows : []"
				:total="isSegmented ? hoverTotal : null"
				:stats="hoverStats"
			/>

			<!-- Axis labels: left gutter (vertical) or below each stage (horizontal) -->
			<div class="bgl-funnel__axis">
				<div
					v-for="(s, i) in segments" :key="i" class="bgl-funnel__tick"
					:style="horizontal ? { flex: '1 1 0' } : { height: `${bandHeight}px` }"
				>
					<span class="bgl-funnel__name ellipsis-1 txt14" :title="s.label">{{ s.label }}</span>
					<span class="bgl-funnel__val txt13">{{ s.display }}</span>
				</div>
			</div>
		</div>

		<!-- Source legend (segmented) -->
		<ul v-if="flow && isSegmented" class="bgl-funnel__legend flex flex-wrap justify-content-center">
			<li v-for="(src, i) in sources" :key="i" class="flex align-items-center">
				<span class="bgl-funnel__dot" :style="{ background: src.color }" />{{ src.name }}
			</li>
		</ul>

		<!-- Discrete bars (flow=false) -->
		<template v-if="!flow">
			<div v-for="(s, i) in stages" :key="i" class="bgl-funnel__stage">
				<span class="bgl-funnel__name txt-0875rem ellipsis-1" :title="s.label">{{ s.label }}</span>
			<div class="bgl-funnel__bar-wrap flex justify-content-center">
				<div
					v-tooltip="{ content: s.tip, html: true }"
					class="bgl-funnel__bar flex flex-center" :style="{ width: `${Math.max(4, s.fromTop)}%`, background: s.barFill }"
				>
						<span class="bgl-funnel__val semibold">{{ s.display }}</span>
					</div>
				</div>
			</div>
		</template>
	</div>
</template>

<style scoped>
/* Flow */
.bgl-funnel__seg { transition: opacity 0.15s ease; outline: none; }
.bgl-funnel__seg:hover { opacity: 0.85; cursor: default; }
.bgl-funnel__seg:focus, .bgl-funnel__sub:focus, .bgl-funnel__hit:focus { outline: none; }
.bgl-funnel__sub { stroke: var(--bgl-box-bg); stroke-width: 0.4; outline: none; }
.bgl-funnel__hit { cursor: default; outline: none; }
.bgl-funnel__hit:hover { fill: rgba(255, 255, 255, 0.06); }
.bgl-funnel__legend { list-style: none; margin: 0.75rem 0 0; padding: 0; gap: 0.5rem 1rem; font-size: 0.8rem; }
.bgl-funnel__legend li { gap: 0.4rem; }
.bgl-funnel__dot { width: 10px; height: 10px; border-radius: 3px; }
.bgl-funnel__val { font-variant-numeric: tabular-nums; }

/* Vertical flow: silhouette (fills) + right axis gutter */
.bgl-funnel--vertical .bgl-funnel__flow { display: flex; align-items: stretch; gap: 0.75rem; width: 100%; }
.bgl-funnel--vertical .bgl-funnel__svg { flex: 1 1 auto; width: auto; min-width: 0; }
.bgl-funnel--vertical .bgl-funnel__axis { flex: 0 0 auto; display: flex; flex-direction: column; }
.bgl-funnel--vertical .bgl-funnel__tick {
	display: flex; flex-direction: column; justify-content: center; line-height: 1.3; max-width: 12rem;
}

/* Horizontal flow: silhouette on top, labels below each stage */
.bgl-funnel--horizontal .bgl-funnel__flow { display: flex; flex-direction: column; gap: 0.4rem; }
.bgl-funnel--horizontal .bgl-funnel__svg { width: 100%; }
.bgl-funnel--horizontal .bgl-funnel__axis { display: flex; }
.bgl-funnel--horizontal .bgl-funnel__tick {
	display: flex; flex-direction: column; align-items: center; text-align: center; line-height: 1.15; overflow: hidden; padding: 0 0.25rem;
}

/* Bars */
.bgl-funnel--bars { display: grid; gap: 0.5rem; }
.bgl-funnel__stage { display: grid; grid-template-columns: minmax(60px, 22%) 1fr; align-items: center; gap: 0.75rem; }
.bgl-funnel--bars .bgl-funnel__name { color: var(--bgl-text); font-weight: 500; }
.bgl-funnel__bar {
	height: 38px; border-radius: 8px; color: var(--bgl-white); font-weight: 600; font-size: 0.85rem;
	transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
	min-width: 2.5rem; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); cursor: default;
}
</style>
