<script setup lang="ts">
import type { Archon, Labrinth } from '@modrinth/api-client'
import { InfoIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import { computed, onMounted, ref, watch } from 'vue'

import { formatPrice } from '../../../../utils'
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils.ts'
import { regionOverrides } from '../../utils/regions.ts'
import Slider from '../base/Slider.vue'
import ModalLoadingIndicator from '../modal/ModalLoadingIndicator.vue'
import type { RegionPing, ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
import ServersRegionButton from './ServersRegionButton.vue'
import ServersSpecs from './ServersSpecs.vue'

const { formatMessage, locale } = useVIntl()

const props = defineProps<{
	regions: Archon.Servers.v1.Region[]
	pings: RegionPing[]
	fetchStock: (
		region: Archon.Servers.v1.Region,
		request: Archon.Servers.v0.StockRequest,
	) => Promise<number>
	custom: boolean
	currency: string
	interval: ServerBillingInterval
	availableProducts: Labrinth.Billing.Internal.Product[]
}>()

const loading = ref(true)
const checkingCustomStock = ref(false)
const selectedPlan = defineModel<Labrinth.Billing.Internal.Product>('plan')
const selectedRegion = defineModel<string>('region')

const selectedPrice = computed(() => {
	if (!selectedPlan.value) return undefined
	const amount = getPriceForInterval(selectedPlan.value, props.currency, props.interval)
	return amount ? amount / monthsInInterval[props.interval] : undefined
})

const regionOrder: string[] = Object.keys(regionOverrides)

const sortedRegions = computed(() => {
	return props.regions.slice().sort((a, b) => {
		return regionOrder.indexOf(a.shortcode) - regionOrder.indexOf(b.shortcode)
	})
})

const visibleRegions = computed(() => {
	if (!loading.value) {
		return sortedRegions.value.filter((region) => {
			// only show eu-lim if it has stock because we're not restocking it.
			if (region.shortcode === 'eu-lim') {
				return currentStock.value[region.shortcode] > 0
			}
			return true
		})
	}
	return sortedRegions.value
})

const selectedRam = ref<number>(-1)

const ramOptions = computed(() => {
	return props.availableProducts
		.map((product) => {
			const metadata = product.metadata
			if (metadata.type === 'pyro' || metadata.type === 'medal') {
				return metadata.ram / 1024
			}
			return 0
		})
		.filter((x) => x > 0)
})

const minRam = computed(() => {
	return Math.min(...ramOptions.value)
})
const maxRam = computed(() => {
	return Math.max(...ramOptions.value)
})

const lowestProduct = computed(() => {
	return (
		props.availableProducts.find((product) => {
			const metadata = product.metadata
			return (
				(metadata.type === 'pyro' || metadata.type === 'medal') &&
				metadata.ram / 1024 === minRam.value
			)
		}) ?? props.availableProducts[0]
	)
})

const selectedPlanSpecs = computed(() => {
	if (!selectedPlan.value) return null
	const metadata = selectedPlan.value.metadata
	if (metadata.type === 'pyro' || metadata.type === 'medal') {
		return {
			ram: metadata.ram,
			storage: metadata.storage,
			cpu: metadata.cpu,
		}
	}
	return null
})

function updateRamStock(regionToCheck: string, newRam: number) {
	if (newRam > 0) {
		checkingCustomStock.value = true
		const plan = props.availableProducts.find((product) => {
			const metadata = product.metadata
			return (
				(metadata.type === 'pyro' || metadata.type === 'medal') && metadata.ram / 1024 === newRam
			)
		})
		if (plan) {
			const region = sortedRegions.value.find((region) => region.shortcode === regionToCheck)
			if (region) {
				const metadata = plan.metadata
				if (metadata.type === 'pyro' || metadata.type === 'medal') {
					props
						.fetchStock(region, {
							cpu: metadata.cpu,
							memory_mb: metadata.ram,
							swap_mb: metadata.swap,
							storage_mb: metadata.storage,
						})
						.then((stock: number) => {
							if (stock > 0) {
								selectedPlan.value = plan
							} else {
								selectedPlan.value = undefined
							}
						})
						.finally(() => {
							checkingCustomStock.value = false
						})
				} else {
					checkingCustomStock.value = false
				}
			} else {
				checkingCustomStock.value = false
			}
		}
	}
}

watch(selectedRam, (newRam: number) => {
	if (props.custom && selectedRegion.value) {
		updateRamStock(selectedRegion.value, newRam)
	}
})

watch(selectedRegion, (newRegion: string | undefined) => {
	if (props.custom && newRegion) {
		updateRamStock(newRegion, selectedRam.value)
	}
})

const currentStock = ref<{ [region: string]: number }>({})
const bestPing = ref<string>()

const messages = defineMessages({
	prompt: {
		id: 'servers.region.prompt',
		defaultMessage: 'Where would you like your server to be located?',
	},
	regionUnsupported: {
		id: 'servers.region.region-unsupported',
		defaultMessage: `Region not listed? <link>Let us know where you'd like to see Modrinth Hosting next!</link>`,
	},
	customPrompt: {
		id: 'servers.region.custom.prompt',
		defaultMessage: `How much RAM do you want your server to have?`,
	},
})

async function updateStock() {
	currentStock.value = {}

	const getStockRequest = (
		product: Labrinth.Billing.Internal.Product,
	): Archon.Servers.v0.StockRequest => {
		const metadata = product.metadata
		if (metadata.type === 'pyro' || metadata.type === 'medal') {
			return {
				cpu: metadata.cpu,
				memory_mb: metadata.ram,
				swap_mb: metadata.swap,
				storage_mb: metadata.storage,
			}
		}
		return { cpu: 0, memory_mb: 0, swap_mb: 0, storage_mb: 0 }
	}

	const capacityChecks = sortedRegions.value.map((region) =>
		props.fetchStock(
			region,
			selectedPlan.value
				? getStockRequest(selectedPlan.value)
				: getStockRequest(lowestProduct.value),
		),
	)
	const results = await Promise.all(capacityChecks)
	results.forEach((result, index) => {
		currentStock.value[sortedRegions.value[index].shortcode] = result
	})
}

onMounted(() => {
	// auto select region with lowest ping
	loading.value = true
	bestPing.value =
		props.pings.length > 0
			? props.pings.reduce((acc, cur) => {
					return acc.ping < cur.ping ? acc : cur
				})?.region
			: undefined
	selectedRegion.value = undefined
	selectedRam.value = minRam.value
	checkingCustomStock.value = true
	updateStock().then(() => {
		const firstWithStock = sortedRegions.value.find(
			(region) => currentStock.value[region.shortcode] > 0,
		)
		let stockedRegion = selectedRegion.value
		if (!stockedRegion) {
			stockedRegion =
				bestPing.value && currentStock.value[bestPing.value] > 0
					? bestPing.value
					: firstWithStock?.shortcode
		}
		selectedRegion.value = stockedRegion
		if (props.custom && stockedRegion) {
			updateRamStock(stockedRegion, minRam.value)
		}
		loading.value = false
	})
})
</script>

<template>
	<ModalLoadingIndicator v-if="loading" class="flex py-40 justify-center">
		Checking availability...
	</ModalLoadingIndicator>
	<template v-else>
		<h2 class="mt-0 mb-4 text-xl font-bold text-contrast">
			{{ formatMessage(messages.prompt) }}
		</h2>
		<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
			<ServersRegionButton
				v-for="region in visibleRegions"
				:key="region.shortcode"
				v-model="selectedRegion"
				:region="region"
				:out-of-stock="currentStock[region.shortcode] === 0"
				:ping="pings.find((p) => p.region === region.shortcode)?.ping"
				:best-ping="bestPing === region.shortcode"
			/>
		</div>
		<div class="mt-3 text-sm">
			<IntlFormatted :message-id="messages.regionUnsupported">
				<template #link="{ children }">
					<a
						class="text-link"
						target="_blank"
						rel="noopener noreferrer"
						href="https://surveys.modrinth.com/servers-region-waitlist"
					>
						<component :is="() => children" />
					</a>
				</template>
			</IntlFormatted>
		</div>
		<template v-if="custom">
			<h2 class="mt-4 mb-2 text-xl font-bold text-contrast">
				{{ formatMessage(messages.customPrompt) }}
			</h2>
			<div>
				<Slider v-model="selectedRam" :min="minRam" :max="maxRam" :step="2" unit="GB" />
				<p v-if="selectedPrice" class="mt-2 mb-0">
					<span class="text-contrast text-lg font-bold"
						>{{ formatPrice(locale, selectedPrice, currency, true) }} / month</span
					><span v-if="interval !== 'monthly'">, billed {{ interval }}</span>
				</p>
				<div class="bg-bg rounded-xl p-4 mt-2 text-secondary h-14">
					<div v-if="checkingCustomStock" class="flex gap-2 items-center">
						<SpinnerIcon class="size-5 shrink-0 animate-spin" /> Checking availability...
					</div>
					<div v-else-if="selectedPlanSpecs">
						<ServersSpecs
							class="!flex-row justify-between"
							:ram="selectedPlanSpecs.ram"
							:storage="selectedPlanSpecs.storage"
							:cpus="selectedPlanSpecs.cpu"
						/>
					</div>
					<div v-else class="flex gap-2 items-center">
						<XIcon class="size-5 shrink-0 text-red" /> Sorry, we don't have any plans available with
						{{ selectedRam }} GB RAM in this region.
					</div>
				</div>
				<div class="flex gap-2 mt-2">
					<InfoIcon class="hidden sm:block shrink-0 mt-1" />
					<span class="text-sm text-secondary">
						Storage and shared CPU count are currently not configurable independently, and are based
						on the amount of RAM you select.
					</span>
				</div>
			</div>
		</template>
	</template>
</template>
