import {
	Editor,
	TLAssetId,
	TLImageAsset,
	TLImageShape,
	TLShapeId,
	TLVideoAsset,
	TLVideoShape,
	debounce,
	react,
	useDelaySvgExport,
	useEditor,
	useSvgExportContext,
} from '@tldraw/editor'
import { useEffect, useMemo, useRef, useState } from 'react'

/**
 * This is a handy helper hook that resolves an asset to an optimized URL for a given shape, or its
 * {@link @tldraw/editor#Editor.createTemporaryAssetPreview | placeholder} if the asset is still
 * uploading. This is used in particular for high-resolution images when you want lower and higher
 * resolution depending on the context.
 *
 * For image scaling to work, you need to implement scaled URLs in
 * {@link @tldraw/tlschema#TLAssetStore.resolve}.
 *
 * @public
 */
export function useImageOrVideoAsset({
	shapeId,
	assetId,
}: {
	shapeId: TLShapeId
	assetId: TLAssetId | null
}) {
	const editor = useEditor()
	const isExport = !!useSvgExportContext()
	const isReady = useDelaySvgExport()

	const resolveAssetUrlDebounced = useMemo(() => debounce(resolveAssetUrl, 500), [])

	// We use a state to store the result of the asset resolution, and we're going to avoid updating this whenever we can
	const [result, setResult] = useState<{
		asset: (TLImageAsset | TLVideoAsset) | null
		url: string | null
	}>(() => ({
		asset: assetId ? editor.getAsset<TLImageAsset | TLVideoAsset>(assetId) ?? null : null,
		url: null as string | null,
	}))

	// A flag for whether we've resolved the asset URL at least once, after which we can debounce
	const didAlreadyResolve = useRef(false)

	// The last URL that we've seen for the shape
	const previousUrl = useRef<string | null>(null)

	useEffect(() => {
		if (!assetId) return

		let isCancelled = false
		let cancelDebounceFn: (() => void) | undefined

		const cleanupEffectScheduler = react('update state', () => {
			if (!isExport && editor.getCulledShapes().has(shapeId)) return

			// Get the fresh asset
			const asset = editor.getAsset<TLImageAsset | TLVideoAsset>(assetId)
			if (!asset) return

			// Get the fresh shape
			const shape = editor.getShape<TLImageShape | TLVideoShape>(shapeId)
			if (!shape) return

			// Set initial preview for the shape if it has no source (if it was pasted into a local project as base64)
			if (!asset.props.src) {
				const preview = editor.getTemporaryAssetPreview(asset.id)
				if (preview) {
					if (previousUrl.current !== preview) {
						previousUrl.current = preview // just for kicks, let's save the url as the previous URL
						setResult((prev) => ({ ...prev, isPlaceholder: true, url: preview })) // set the preview as the URL
						isReady() // let the SVG export know we're ready for export
					}
					return
				}
			}

			// aside ...we could bail here if the only thing that has changed is the shape has changed from culled to not culled

			const screenScale = editor.getZoomLevel() * (shape.props.w / asset.props.w)

			function resolve(asset: TLImageAsset | TLVideoAsset, url: string | null) {
				if (isCancelled) return // don't update if the hook has remounted
				if (previousUrl.current === url) return // don't update the state if the url is the same
				didAlreadyResolve.current = true // mark that we've resolved our first image
				previousUrl.current = url // keep the url around to compare with the next one
				setResult({ asset, url })
				isReady() // let the SVG export know we're ready for export
			}

			// If we already resolved the URL, debounce fetching potentially multiple image variations.
			if (didAlreadyResolve.current) {
				resolveAssetUrlDebounced(editor, assetId, screenScale, isExport, (url) =>
					resolve(asset, url)
				)
				cancelDebounceFn = resolveAssetUrlDebounced.cancel // cancel the debounce when the hook unmounts
			} else {
				resolveAssetUrl(editor, assetId, screenScale, isExport, (url) => resolve(asset, url))
			}
		})

		return () => {
			cleanupEffectScheduler()
			cancelDebounceFn?.()
			isCancelled = true
		}
	}, [editor, assetId, isExport, isReady, shapeId, resolveAssetUrlDebounced])

	return result
}

function resolveAssetUrl(
	editor: Editor,
	assetId: TLAssetId,
	screenScale: number,
	isExport: boolean,
	callback: (url: string | null) => void
) {
	editor
		.resolveAssetUrl(assetId, {
			screenScale,
			shouldResolveToOriginal: isExport,
		})
		// There's a weird bug with out debounce function that doesn't
		// make it work right with async functions, so we use a callback
		// here instead of returning a promise.
		.then((url) => {
			callback(url)
		})
}

/**
 * @deprecated Use {@link useImageOrVideoAsset} instead.
 *
 * @public
 */
export const useAsset = useImageOrVideoAsset
