import { safeParseUrl } from '@tldraw/utils'
import { T } from '@tldraw/validate'
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'
import { RecordProps } from '../recordsWithProps'
import { TLBaseShape } from './TLBaseShape'

// Only allow multiplayer embeds. If we add additional routes later for example '/help' this won't match
const TLDRAW_APP_RE = /(^\/r\/[^/]+\/?$)/

const EMBED_DEFINITIONS = [
	{
		hostnames: ['beta.tldraw.com', 'tldraw.com', 'localhost:3000'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(TLDRAW_APP_RE)) {
				return url
			}
			return
		},
	},
	{
		hostnames: ['figma.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) {
				const outUrl = urlObj.searchParams.get('url')
				if (outUrl) {
					return outUrl
				}
			}
			return
		},
	},
	{
		hostnames: ['google.*'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (!urlObj) return

			const matches = urlObj.pathname.match(/^\/maps\/embed\/v1\/view\/?$/)
			if (matches && urlObj.searchParams.has('center') && urlObj.searchParams.get('zoom')) {
				const zoom = urlObj.searchParams.get('zoom')
				const [lat, lon] = urlObj.searchParams.get('center')!.split(',')
				return `https://www.google.com/maps/@${lat},${lon},${zoom}z`
			}
			return
		},
	},
	{
		hostnames: ['val.town'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			// e.g. extract "steveruizok/mathFact" from https://www.val.town/v/steveruizok/mathFact
			const matches = urlObj && urlObj.pathname.match(/\/embed\/(.+)\/?/)
			if (matches) {
				return `https://www.val.town/v/${matches[1]}`
			}
			return
		},
	},
	{
		hostnames: ['codesandbox.io'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/)
			if (matches) {
				return `https://codesandbox.io/s/${matches[1]}`
			}
			return
		},
	},
	{
		hostnames: ['codepen.io'],
		fromEmbedUrl: (url: string) => {
			const CODEPEN_EMBED_REGEXP = /https:\/\/codepen.io\/([^/]+)\/embed\/([^/]+)/
			const matches = url.match(CODEPEN_EMBED_REGEXP)
			if (matches) {
				const [_, user, id] = matches
				return `https://codepen.io/${user}/pen/${id}`
			}
			return
		},
	},
	{
		hostnames: ['scratch.mit.edu'],
		fromEmbedUrl: (url: string) => {
			const SCRATCH_EMBED_REGEXP = /https:\/\/scratch.mit.edu\/projects\/embed\/([^/]+)/
			const matches = url.match(SCRATCH_EMBED_REGEXP)
			if (matches) {
				const [_, id] = matches
				return `https://scratch.mit.edu/projects/${id}`
			}
			return
		},
	},
	{
		hostnames: ['*.youtube.com', 'youtube.com', 'youtu.be'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (!urlObj) return

			const hostname = urlObj.hostname.replace(/^www./, '')
			if (hostname === 'youtube.com') {
				const matches = urlObj.pathname.match(/^\/embed\/([^/]+)\/?/)
				if (matches) {
					return `https://www.youtube.com/watch?v=${matches[1]}`
				}
			}
			return
		},
	},
	{
		hostnames: ['calendar.google.*'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			const srcQs = urlObj?.searchParams.get('src')

			if (urlObj?.pathname.match(/\/calendar\/embed/) && srcQs) {
				urlObj.pathname = '/calendar/u/0'
				const keys = Array.from(urlObj.searchParams.keys())
				for (const key of keys) {
					urlObj.searchParams.delete(key)
				}
				urlObj.searchParams.set('cid', srcQs)
				return urlObj.href
			}
			return
		},
	},
	{
		hostnames: ['docs.google.*'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)

			if (urlObj?.pathname.match(/^\/presentation/) && urlObj?.pathname.match(/\/embed\/?$/)) {
				urlObj.pathname = urlObj.pathname.replace(/\/embed$/, '/pub')
				const keys = Array.from(urlObj.searchParams.keys())
				for (const key of keys) {
					urlObj.searchParams.delete(key)
				}
				return urlObj.href
			}
			return
		},
	},
	{
		hostnames: ['gist.github.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(/\/([^/]+)\/([^/]+)/)) {
				if (!url.split('/').pop()) return
				return url
			}
			return
		},
	},
	{
		hostnames: ['replit.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (
				urlObj &&
				urlObj.pathname.match(/\/@([^/]+)\/([^/]+)/) &&
				urlObj.searchParams.has('embed')
			) {
				urlObj.searchParams.delete('embed')
				return urlObj.href
			}
			return
		},
	},
	{
		hostnames: ['felt.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(/^\/embed\/map\//)) {
				urlObj.pathname = urlObj.pathname.replace(/^\/embed/, '')
				return urlObj.href
			}
			return
		},
	},
	{
		hostnames: ['open.spotify.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(/^\/embed\/(artist|album)\//)) {
				return urlObj.origin + urlObj.pathname.replace(/^\/embed/, '')
			}
			return
		},
	},
	{
		hostnames: ['vimeo.com', 'player.vimeo.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.hostname === 'player.vimeo.com') {
				const matches = urlObj.pathname.match(/^\/video\/([^/]+)\/?$/)
				if (matches) {
					return 'https://vimeo.com/' + matches[1]
				}
			}
			return
		},
	},
	{
		hostnames: ['excalidraw.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.hash.match(/#room=/)) {
				return url
			}
			return
		},
	},
	{
		hostnames: ['observablehq.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (urlObj && urlObj.pathname.match(/^\/embed\/@([^/]+)\/([^/]+)\/?$/)) {
				return `${urlObj.origin}${urlObj.pathname.replace('/embed', '')}#cell-*`
			}
			if (urlObj && urlObj.pathname.match(/^\/embed\/([^/]+)\/?$/)) {
				return `${urlObj.origin}${urlObj.pathname.replace('/embed', '/d')}#cell-*`
			}

			return
		},
	},
	{
		hostnames: ['desmos.com'],
		fromEmbedUrl: (url: string) => {
			const urlObj = safeParseUrl(url)
			if (
				urlObj &&
				urlObj.hostname === 'www.desmos.com' &&
				urlObj.pathname.match(/^\/calculator\/([^/]+)\/?$/) &&
				urlObj.search === '?embed' &&
				urlObj.hash === ''
			) {
				return url.replace('?embed', '')
			}
			return
		},
	},
]

/** @public */
export interface TLEmbedShapeProps {
	w: number
	h: number
	url: string
}

/** @public */
export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps>

/** @public */
export const embedShapeProps: RecordProps<TLEmbedShape> = {
	w: T.nonZeroNumber,
	h: T.nonZeroNumber,
	url: T.string,
}

const Versions = createShapePropsMigrationIds('embed', {
	GenOriginalUrlInEmbed: 1,
	RemoveDoesResize: 2,
	RemoveTmpOldUrl: 3,
	RemovePermissionOverrides: 4,
})

export { Versions as embedShapeVersions }

/** @public */
export const embedShapeMigrations = createShapePropsMigrationSequence({
	sequence: [
		{
			id: Versions.GenOriginalUrlInEmbed,
			// add tmpOldUrl property
			up: (props) => {
				try {
					const url = props.url
					const host = new URL(url).host.replace('www.', '')
					let originalUrl
					for (const localEmbedDef of EMBED_DEFINITIONS) {
						if (localEmbedDef.hostnames.includes(host)) {
							try {
								originalUrl = localEmbedDef.fromEmbedUrl(url)
							} catch (err) {
								console.warn(err)
							}
						}
					}

					props.tmpOldUrl = props.url
					props.url = originalUrl ?? ''
				} catch {
					props.url = ''
					props.tmpOldUrl = props.url
				}
			},
			down: 'retired',
		},
		{
			id: Versions.RemoveDoesResize,
			up: (props) => {
				delete props.doesResize
			},
			down: 'retired',
		},
		{
			id: Versions.RemoveTmpOldUrl,
			up: (props) => {
				delete props.tmpOldUrl
			},
			down: 'retired',
		},
		{
			id: Versions.RemovePermissionOverrides,
			up: (props) => {
				delete props.overridePermissions
			},
			down: 'retired',
		},
	],
})
