<script lang="ts" setup>
import type { PanelConfig, PanelState } from '../../composables/useResizableLayout'
import { computed, inject, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
import { RESIZABLE_KEY, nextPanelId } from '../../composables/useResizableLayout'

const props = withDefaults(defineProps<Props>(), {
	defaultSize: 200,
	min: 0,
	max: Number.POSITIVE_INFINITY,
	collapsible: false,
	collapsed: undefined,
	flex: false,
	handle: true,
})

const emit = defineEmits<{
	'update:collapsed': [value: boolean]
	'update:size': [value: number]
}>()

interface Props {
	id?: string
	/** Initial size in pixels. @default 200 */
	defaultSize?: number
	/** Controlled size — use with v-model:size to drive from outside (e.g. device presets). */
	size?: number
	/** Minimum size in pixels. @default 0 */
	min?: number
	/** Maximum size in pixels. @default Infinity */
	max?: number
	/** Allows the panel to be collapsed. Exposes toggleCollapse via slot + v-model:collapsed. */
	collapsible?: boolean
	/** Controlled collapsed state — use with v-model:collapsed. */
	collapsed?: boolean
	/** Fill all remaining space in the layout. */
	flex?: boolean
	/** Collapse this panel when the layout switches to mobile. */
	collapseOnMobile?: boolean
	/** Hide this panel entirely when the layout switches to mobile. */
	hideOnMobile?: boolean
	/** Show a drag handle on the trailing edge of this panel. @default true */
	handle?: boolean
}

const ctx = inject(RESIZABLE_KEY)!
if (!ctx) throw new Error('<Panel> must be used inside a <Resizable>')

// Stable ID for the lifetime of this instance
const panelId = props.id ?? nextPanelId()

const panelState = shallowRef<PanelState | null>(null)
const panelEl = ref<HTMLElement | null>(null)

// ── Collapsed (controlled / uncontrolled) ──────────────────────────────────

const collapsedInternal = computed({
	get: () => props.collapsed ?? panelState.value?.collapsed.value ?? false,
	set(val) {
		if (panelState.value) panelState.value.collapsed.value = val
		emit('update:collapsed', val)
	},
})

watch(() => props.collapsed, (val) => {
	if (val !== undefined && panelState.value) panelState.value.collapsed.value = val
})

// ── Size (controlled / uncontrolled) ──────────────────────────────────────

// Prop → internal (button presets, external control)
watch(() => props.size, (val) => {
	if (val === undefined || !panelState.value) return
	const clamped = Math.max(props.min, Math.min(props.max, val))
	if (clamped !== panelState.value.size.value) panelState.value.size.value = clamped
})

// Internal → prop (drag end syncs back up via v-model:size)
// Skip the initial mount transition (undefined → number) to avoid spurious emits
watch(
	() => panelState.value?.size.value,
	(newSize, oldSize) => {
		if (newSize !== undefined && oldSize !== undefined) emit('update:size', newSize)
	},
)

// ── Lifecycle ──────────────────────────────────────────────────────────────

onMounted(() => {
	const config: PanelConfig = {
		id: panelId,
		defaultSize: props.size ?? props.defaultSize,
		min: props.min,
		max: props.max,
		flex: props.flex,
		collapseOnMobile: props.collapseOnMobile ?? false,
		hideOnMobile: props.hideOnMobile ?? false,
	}
	panelState.value = ctx.registerPanel(config)
	panelState.value.el.value = panelEl.value
	if (props.collapsed !== undefined) panelState.value.collapsed.value = props.collapsed
})

onUnmounted(() => { ctx.unregisterPanel(panelId) })

// ── Computed state ─────────────────────────────────────────────────────────

const size = computed(() => panelState.value?.size.value ?? props.defaultSize)
const isCollapsed = computed(() => collapsedInternal.value)
const isHorizontal = computed(() => ctx.direction.value === 'horizontal')
const isHidden = computed(() => ctx.isMobile.value && props.hideOnMobile)

// Default to false (show handle) before mount so handles appear on first render.
// After registration the correct last-panel is identified reactively.
const isLastPanel = computed(() => {
	const ps = ctx.panels.value
	const idx = ps.findIndex(p => p.id === panelId)
	return idx >= 0 && idx === ps.length - 1
})

const panelStyle = computed(() => {
	if (isCollapsed.value) {
		return isHorizontal.value
			? { flex: '0 0 0px', width: '0px', minWidth: '0', overflow: 'hidden' }
			: { flex: '0 0 0px', height: '0px', minHeight: '0', overflow: 'hidden' }
	}
	if (props.flex) {
		return isHorizontal.value
			? { flex: '1 1 0', minWidth: `${props.min}px`, maxWidth: props.max === Number.POSITIVE_INFINITY ? undefined : `${props.max}px`, overflow: 'hidden' }
			: { flex: '1 1 0', minHeight: `${props.min}px`, maxHeight: props.max === Number.POSITIVE_INFINITY ? undefined : `${props.max}px`, overflow: 'hidden' }
	}
	return isHorizontal.value
		? { flex: `0 0 ${size.value}px`, width: `${size.value}px`, overflow: 'hidden' }
		: { flex: `0 0 ${size.value}px`, height: `${size.value}px`, overflow: 'hidden' }
})

// ── Exposed / slot API ─────────────────────────────────────────────────────

function toggleCollapse() {
	collapsedInternal.value = !collapsedInternal.value
}

defineExpose({ toggleCollapse, size, isCollapsed })
</script>

<template>
	<div
		v-if="!isHidden" ref="panelEl" class="panel"
		:class="[isHorizontal ? 'panel--h' : 'panel--v', isCollapsed && 'panel--collapsed']" :style="panelStyle"
	>
		<div class="panel__content">
			<slot :collapsed="isCollapsed" :size="size" :toggle-collapse="toggleCollapse" />
		</div>

		<div
			v-if="props.handle && !isLastPanel && !isCollapsed" class="panel__handle"
			:class="isHorizontal ? 'panel__handle--h' : 'panel__handle--v'"
			@pointerdown="ctx.onHandlePointerDown(panelId, $event)"
		>
			<div class="panel__handle-bar" />
		</div>
	</div>
</template>

<style scoped>
.panel {
	position: relative;
	display: flex;
	overflow: hidden;
	/* transition: flex-basis 0.2s ease, width 0.2s ease, height 0.2s ease; */
}

/* Disable transitions while dragging so the handle tracks the pointer exactly */
:global(.resizable[data-resizing]) .panel {
	transition: none !important;
}

/* Prevent iframes from absorbing pointer events and triggering expensive
   cross-process reflows on every resize tick */
:global(.resizable[data-resizing]) iframe,
:global(.resizable[data-resizing]) video {
	pointer-events: none !important;
}

.panel--h {
	flex-direction: row;
}

.panel--v {
	flex-direction: column;
}

.panel__content {
	flex: 1 1 0;
	overflow: hidden;
	min-width: 0;
	min-height: 0;
}

/* ─── Handle ─────────────────────────────────────────────────────────────── */

.panel__handle {
	flex-shrink: 0;
	position: relative;
	z-index: 10;
	touch-action: none;
	user-select: none;
	display: flex;
	align-items: center;
	justify-content: center;
}

.panel__handle--h {
	width: var(--resizable-handle-size, 9px);
	cursor: ew-resize;
}

.panel__handle--v {
	height: var(--resizable-handle-size, 9px);
	cursor: ns-resize;
}

/* Persistent 1px divider */
.panel__handle::before {
	content: '';
	position: absolute;
	border-radius: 9999px;
	background: var(--resizable-divider-color, color-mix(in srgb, currentColor 14%, transparent));
	transition: background 0.15s;
}

.panel__handle--h::before {
	top: 0;
	bottom: 0;
	left: 50%;
	transform: translateX(-50%);
	width: 1px;
	height: 100%;
}

.panel__handle--v::before {
	left: 0;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	height: 1px;
	width: 100%;
}

/* Grip pill — fades in on hover */
.panel__handle-bar {
	position: relative;
	z-index: 1;
	border-radius: 9999px;
	background: var(--resizable-grip-color, var(--color-primary, #3b82f6));
	opacity: 0;
	transition: opacity 0.15s, transform 0.15s;
	transform: scale(0.8);
}

.panel__handle--h .panel__handle-bar {
	width: 3px;
	height: 24px;
}

.panel__handle--v .panel__handle-bar {
	height: 3px;
	width: 24px;
}

.panel__handle:hover::before {
	background: var(--resizable-divider-active-color, var(--color-primary, #3b82f6));
}

.panel__handle:hover .panel__handle-bar {
	opacity: 1;
	transform: scale(1);
}
</style>
