<script setup lang="ts">
import type { Options as SignaturePadOptions } from 'signature_pad'
import { Btn } from '@bagelink/vue'
import { useEventListener, useResizeObserver } from '@vueuse/core'
import SignaturePad from 'signature_pad'
import { computed, onMounted, ref, watch } from 'vue'
import type { BagelInputShellProps } from './bagelInputShell'
import { useBagelInputShell } from './bagelInputShell'

export interface WaterMark {
	text: string
	x: number
	y: number
	sx: number
	sy: number
	font: string
	fillStyle: string
	strokeStyle: string
	style: string
}

export type FormatType = 'image/png' | 'image/jpg' | 'image/jpeg' | 'image/svg+xml'

const props = withDefaults(defineProps<{
	sigOption?: SignaturePadOptions
	width?: string
	height?: string
	ratio?: number
	clearOnResize?: boolean
	waterMark?: WaterMark
	disabled?: boolean
	defaultUrl?: string
	format?: FormatType
	clearable?: boolean
	required?: boolean
	label?: string
	helptext?: string
	penColor?: string
	backgroundColor?: string
} & BagelInputShellProps>(), {
	clearable: true,
})

const { shellClass, shellStyle } = useBagelInputShell(props)

const sigData = defineModel()
const fileData = defineModel<File>('file')

const vCanvas = ref<HTMLCanvasElement>()

const sig = ref<SignaturePad>()

const _isEmpty = ref<boolean>(true)

function clear() {
	sig.value?.clear()
	_isEmpty.value = true
	sigData.value = undefined
}

function save(format?: FormatType) {
	format = format || props.format
	return format ? sig.value?.toDataURL(format) : sig.value?.toDataURL()
}

function saveFile() {
	const data = save()
	if (!data) { return }

	if (props.format === 'image/svg+xml') {
		// For SVG, create file directly from the SVG string
		fileData.value = new File([data], 'signature.svg', { type: 'image/svg+xml' })
	} else {
		try {
			// For PNG/JPG, convert base64 to blob first
			const base64Data = data.split(',')[1]
			if (!base64Data) { throw new Error('Invalid data URL format') }

			const byteCharacters = atob(base64Data)
			// Create byte array directly with mapping function
			const byteArray = new Uint8Array(
				Array.from(byteCharacters, char => char.charCodeAt(0))
			)

			const mimeType = props.format || 'image/png'
			const blob = new Blob([byteArray], { type: mimeType })
			const extension = mimeType.split('/')[1]

			fileData.value = new File([blob], `signature.${extension}`, { type: mimeType })
		} catch (error) {
			console.error('Error saving signature file:', error)
		}
	}
}

function fromDataURL(url: string) {
	return sig.value?.fromDataURL(url, {
		height: vCanvas.value?.height,
		width: vCanvas.value?.width,
		ratio: props.ratio,
		xOffset: 0,
		yOffset: 0,
	})
}

const isEmpty = () => sig.value?.isEmpty()
function undo() {
	const data = sig.value?.toData()
	if (data) {
		data.pop()
		sig.value?.fromData(data)
	}
}

const defaultOptions = ref<SignaturePadOptions>({
	penColor: 'rgb(0, 0, 0)',
	backgroundColor: 'rgba(0,0,0,0)',
})

const signatureOptions = computed(() => ({
	...defaultOptions.value,
	...(props.penColor ? { penColor: props.penColor } : {}),
	...(props.backgroundColor ? { backgroundColor: props.backgroundColor } : {}),
	...props.sigOption,
}))

watch(
	() => props.disabled,
	(val) => {
		if (val) { sig.value?.off() }
		else { sig.value?.on() }
	},
)

watch(signatureOptions, (opts) => {
	if (!sig.value) { return }
	sig.value.penColor = opts.penColor ?? 'rgb(0,0,0)'
	if (opts.backgroundColor) {
		sig.value.backgroundColor = opts.backgroundColor
	}
})

function addWaterMark(data: WaterMark) {
	if (!(Object.prototype.toString.call(data) === '[object Object]')) {
		throw new Error(`Expected Object, got ${typeof data}.`)
	} else {
		const textData = {
			text: data.text || '',
			x: data.x || 20,
			y: data.y || 20,
			sx: data.sx || 40,
			sy: data.sy || 40,
		}

		const ctx = vCanvas.value?.getContext('2d')
		if (!ctx) { return }
		ctx.font = data.font || '20px sans-serif'
		ctx.fillStyle = data.fillStyle || '#333'
		ctx.strokeStyle = data.strokeStyle || '#333'
		if (data.style === 'all') {
			ctx.fillText(textData.text, textData.x, textData.y)
			ctx.strokeText(textData.text, textData.sx, textData.sy)
		} else if (data.style === 'stroke') {
			ctx.strokeText(textData.text, textData.sx, textData.sy)
		} else {
			ctx.fillText(textData.text, textData.x, textData.y)
		}
	}
}

function onEndStroke() {
	_isEmpty.value = sig.value?.isEmpty() ?? false
	const data = save()
	sigData.value = data
	saveFile()
}

function loadFile(file: File | undefined) {
	if (!file) { return }
	const reader = new FileReader()
	return new Promise((resolve) => {
		reader.onload = (e) => {
			const dataUrl = String(e.target?.result)
			void fromDataURL(dataUrl)
			_isEmpty.value = false
			sigData.value = dataUrl
			resolve(true)
		}
		reader.readAsDataURL(file)
	})
}

watch(fileData, (newFile) => {
	void loadFile(newFile)
})

function resizeCanvas() {
	let url
	if (!isEmpty()) { url = save() }
	const ratio = props.ratio ?? 1 / 2
	const { width } = vCanvas.value!.getBoundingClientRect()
	vCanvas.value!.width = props.width ? Number(props.width.replaceAll('px', '')) : width
	vCanvas.value!.height = props.height ? Number(props.height.replaceAll('px', '')) : width * ratio
	clear()
	if (!props.clearOnResize && url !== undefined) { void fromDataURL(url) }
	if (props.waterMark) { addWaterMark(props.waterMark) }
}

function draw() {
	sig.value = new SignaturePad(vCanvas.value!, signatureOptions.value)
	resizeCanvas()
	if (props.disabled) { sig.value.off() }
	else { sig.value.on() }
	_isEmpty.value = sig.value.isEmpty()

	// Load initial file if provided
	if (fileData.value) {
		void loadFile(fileData.value)
	}
}

onMounted(draw)
useResizeObserver(() => vCanvas.value, resizeCanvas)
useEventListener(() => sig.value, 'endStroke', onEndStroke)

defineExpose({
	save,
	clear,
	isEmpty,
	undo,
	addWaterMark,
	fromDataURL,
})
</script>

<template>
	<div
		class="bagel-input bgl_input signature-pad relative"
		:class="[shellClass, { 'has-value': !_isEmpty }]" :style="shellStyle"
		tabindex="-1" @touchmove.prevent
	>
		<label v-if="label" class="label-text">{{ label }}</label>
		<p v-if="helptext" class="helptext">{{ helptext }}</p>
		<Btn
			v-if="clearable && !disabled"
			flat
			thin
			icon="refresh"
			class="position-bottom-end"
			@click="clear"
		/>
		<canvas
			ref="vCanvas"
			class="canvas"
			:disabled
			:style="backgroundColor ? { background: backgroundColor } : {}"
		/>
		<input
			placeholder="required"
			type="text"
			:required="required && _isEmpty"
			class="pixel opacity-0"
			inputMode="none"
		>
		<!--
			we add a hidden input to make the signature pad required
					we need to set inputMode="none"
			! this prevents the keyboard from opening on mobile
		-->
	</div>
</template>

<style>

.bgl_input .canvas {
width: 100%;
height: 100%;
}

.bgl_input.signature-pad {
background: transparent;
border: none;
border-radius: var(--bgl-input-border-radius);
}

.bgl_input.signature-pad .canvas {
background: var(--bgl-input-bg);
border-radius: var(--bgl-input-border-radius);
}

.bgl_input.signature-pad > .label-text {
display: block;
font-size: var(--bgl-label-font-size);
margin-bottom: 0.25rem;
}

.bgl_input.signature-pad > .helptext {
font-size: var(--bgl-label-font-size);
margin: 0 0 0.25rem 0;
line-height: 1.4;
opacity: 0.7;
}

.bgl_input.signature-pad .canvas[disabled] {
background: var(--input-disabled-bg);
border: 1px solid var(--bgl-gray);
border-radius: var(--bgl-input-border-radius);
}

</style>
