import type { InjectionKey, Ref } from 'vue'
import { computed, ref, shallowRef, watch } from 'vue'

export interface PanelConfig {
	id: string
	defaultSize: number
	min: number
	max: number
	flex: boolean
	collapseOnMobile: boolean
	hideOnMobile: boolean
}

export interface PanelState {
	id: string
	config: PanelConfig
	size: Ref<number>
	collapsed: Ref<boolean>
	/** DOM reference set by Panel.vue for direct style mutation during drag. */
	el: Ref<HTMLElement | null>
}

export interface ResizableContext {
	direction: Ref<'horizontal' | 'vertical'>
	isMobile: Ref<boolean>
	panels: Ref<PanelState[]>
	registerPanel: (config: PanelConfig) => PanelState
	unregisterPanel: (id: string) => void
	onHandlePointerDown: (panelId: string, event: PointerEvent) => void
}

export const RESIZABLE_KEY: InjectionKey<ResizableContext> = Symbol('Resizable')

let _panelUid = 0
export function nextPanelId(): string {
	return `panel-${++_panelUid}`
}

// ─── Internal drag state ───────────────────────────────────────────────────

interface DragState {
	panelId: string
	pointerId: number
	isHorizontal: boolean
	isRtl: boolean
	startPos: number
	startSizeA: number
	startSizeB: number
	currentSizeA: number
	currentSizeB: number
	indexA: number
	indexB: number
	isFlexA: boolean
	isFlexB: boolean
	elA: HTMLElement | null
	elB: HTMLElement | null
}

// ─── Provider ──────────────────────────────────────────────────────────────

export function useResizableLayoutProvider(options: {
	directionProp: Ref<'horizontal' | 'vertical'>
	mobileDirection: 'horizontal' | 'vertical'
	breakpoint: number
	containerEl: Ref<HTMLElement | undefined>
}) {
	const { containerEl, breakpoint, mobileDirection } = options

	const isMobile = ref(false)
	const panels = shallowRef<PanelState[]>([])

	const direction = computed<'horizontal' | 'vertical'>(() => isMobile.value ? mobileDirection : options.directionProp.value,
	)

	// ── Breakpoint detection ──
	let ro: ResizeObserver | null = null

	function observe() {
		if (!containerEl.value) return
		ro?.disconnect()
		ro = new ResizeObserver((entries) => {
			const entry = entries[0]
			if (entry) isMobile.value = entry.contentRect.width < breakpoint
		})
		ro.observe(containerEl.value)
		isMobile.value = containerEl.value.clientWidth < breakpoint
	}

	function disconnect() {
		ro?.disconnect()
		ro = null
	}

	// On mobile switch: apply collapsed state overrides
	watch(isMobile, (mobile) => {
		for (const panel of panels.value) {
			const { config } = panel
			panel.collapsed.value = mobile
				? (config.collapseOnMobile || false)
				: false
		}
	})

	// ── Panel registry ──
	function registerPanel(config: PanelConfig): PanelState {
		const state: PanelState = {
			id: config.id,
			config,
			size: ref(config.defaultSize),
			collapsed: ref(isMobile.value ? config.collapseOnMobile : false),
			el: ref(null),
		}
		panels.value = [...panels.value, state]
		return state
	}

	function unregisterPanel(id: string) {
		panels.value = panels.value.filter(p => p.id !== id)
	}

	function getPanelIndex(id: string) {
		return panels.value.findIndex(p => p.id === id)
	}

	// ── Drag ──
	// Cleanup function for the current drag's document-level listeners.
	// Called at the start of every pointerdown to clear any previously stuck drag.
	let dragCleanup: (() => void) | null = null

	function flushResize(ds: DragState, currentPos: number) {
		const rawDelta = currentPos - ds.startPos
		const delta = ds.isRtl ? -rawDelta : rawDelta

		const panelA = panels.value[ds.indexA]
		const panelB = panels.value[ds.indexB]
		const minA = panelA.config.min
		const maxA = panelA.config.max
		const minB = panelB.config.min
		const maxB = panelB.config.max

		let newA = ds.currentSizeA
		let newB = ds.currentSizeB

		if (ds.isFlexB) {
			newA = Math.max(minA, Math.min(maxA, ds.startSizeA + delta))
		}
		else if (ds.isFlexA) {
			newB = Math.max(minB, Math.min(maxB, ds.startSizeB - delta))
		}
		else {
			const proposed = Math.max(minA, Math.min(maxA, ds.startSizeA + delta))
			const actualDelta = proposed - ds.startSizeA
			newB = Math.max(minB, Math.min(maxB, ds.startSizeB - actualDelta))
			newA = ds.startSizeA + (ds.startSizeB - newB)
		}

		ds.currentSizeA = newA
		ds.currentSizeB = newB

		// Direct DOM mutation — bypasses Vue scheduler for zero-overhead drag
		const dim = ds.isHorizontal ? 'width' : 'height'
		if (ds.elA && !ds.isFlexA) {
			ds.elA.style.flex = `0 0 ${newA}px`
			ds.elA.style[dim] = `${newA}px`
		}
		if (ds.elB && !ds.isFlexB) {
			ds.elB.style.flex = `0 0 ${newB}px`
			ds.elB.style[dim] = `${newB}px`
		}
	}

	function onHandlePointerDown(panelId: string, event: PointerEvent) {
		// Always clean up any previously stuck drag before starting a new one
		dragCleanup?.()

		const indexA = getPanelIndex(panelId)
		if (indexA < 0 || indexA >= panels.value.length - 1) return

		const panelA = panels.value[indexA]
		const panelB = panels.value[indexA + 1]
		if (panelA.collapsed.value || panelB.collapsed.value) return

		event.preventDefault()

		const isHorizontal = direction.value === 'horizontal'
		const pos = isHorizontal ? event.clientX : event.clientY
		const isRtl = isHorizontal
			&& !!containerEl.value
			&& getComputedStyle(containerEl.value).direction === 'rtl'

		const ds: DragState = {
			panelId,
			pointerId: event.pointerId,
			isHorizontal,
			isRtl,
			startPos: pos,
			startSizeA: panelA.size.value,
			startSizeB: panelB.size.value,
			currentSizeA: panelA.size.value,
			currentSizeB: panelB.size.value,
			indexA,
			indexB: indexA + 1,
			isFlexA: panelA.config.flex,
			isFlexB: panelB.config.flex,
			elA: panelA.el.value,
			elB: panelB.el.value,
		}
		containerEl.value?.setAttribute('data-resizing', '')
		document.body.style.userSelect = 'none'
		document.body.style.cursor = isHorizontal ? 'ew-resize' : 'ns-resize'

		function onMove(e: PointerEvent) {
			if (e.pointerId !== ds.pointerId) return
			flushResize(ds, ds.isHorizontal ? e.clientX : e.clientY)
		}

		function onUp(e: PointerEvent) {
			if (e.pointerId !== ds.pointerId) return
			flushResize(ds, ds.isHorizontal ? e.clientX : e.clientY)
			// Single Vue update at drag end — syncs DOM state back to reactive refs
			panels.value[ds.indexA].size.value = ds.currentSizeA
			panels.value[ds.indexB].size.value = ds.currentSizeB
			cleanup()
		}

		function cleanup() {
			dragCleanup = null
			containerEl.value?.removeAttribute('data-resizing')
			document.body.style.userSelect = ''
			document.body.style.cursor = ''
			document.removeEventListener('pointermove', onMove)
			document.removeEventListener('pointerup', onUp)
			document.removeEventListener('pointercancel', cleanup)
		}

		document.addEventListener('pointermove', onMove)
		document.addEventListener('pointerup', onUp)
		document.addEventListener('pointercancel', cleanup)
		dragCleanup = cleanup
	}

	const context: ResizableContext = {
		direction,
		isMobile,
		panels,
		registerPanel,
		unregisterPanel,
		onHandlePointerDown,
	}

	return { context, observe, disconnect }
}
