<script setup lang="ts">
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 {
	monthsInInterval,
	type ServerBillingInterval,
	type ServerPlan,
	type ServerRegion,
	type ServerStockRequest,
} from '../../utils/billing'
import { regionOverrides } from '../../utils/regions.ts'
import Slider from '../base/Slider.vue'
import ModalLoadingIndicator from '../modal/ModalLoadingIndicator.vue'
import type { RegionPing } from './ModrinthServersPurchaseModal.vue'
import ServersRegionButton from './ServersRegionButton.vue'
import ServersSpecs from './ServersSpecs.vue'

const { formatMessage, locale } = useVIntl()

const props = defineProps<{
	regions: ServerRegion[]
	pings: RegionPing[]
	fetchStock: (region: ServerRegion, request: ServerStockRequest) => Promise<number>
	custom: boolean
	currency: string
	interval: ServerBillingInterval
	availableProducts: ServerPlan[]
}>()

const loading = ref(true)
const checkingCustomStock = ref(false)
const selectedPlan = defineModel<ServerPlan>('plan')
const selectedRegion = defineModel<string>('region')

const selectedPrice = computed(() => {
	const amount = selectedPlan.value?.prices?.find((price) => price.currency_code === props.currency)
		?.prices?.intervals?.[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) => (product.metadata.ram ?? 0) / 1024)
		.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) => (product.metadata.ram ?? 0) / 1024 === minRam.value,
		) ?? props.availableProducts[0]
	)
})

function updateRamStock(regionToCheck: string, newRam: number) {
	if (newRam > 0) {
		checkingCustomStock.value = true
		const plan = props.availableProducts.find(
			(product) => (product.metadata.ram ?? 0) / 1024 === newRam,
		)
		if (plan) {
			const region = sortedRegions.value.find((region) => region.shortcode === regionToCheck)
			if (region) {
				props
					.fetchStock(region, {
						cpu: plan.metadata.cpu ?? 0,
						memory_mb: plan.metadata.ram ?? 0,
						swap_mb: plan.metadata.swap ?? 0,
						storage_mb: plan.metadata.storage ?? 0,
					})
					.then((stock: number) => {
						if (stock > 0) {
							selectedPlan.value = plan
						} else {
							selectedPlan.value = undefined
						}
					})
					.finally(() => {
						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 Servers 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 capacityChecks = sortedRegions.value.map((region) =>
		props.fetchStock(
			region,
			selectedPlan.value
				? {
						cpu: selectedPlan.value?.metadata.cpu ?? 0,
						memory_mb: selectedPlan.value?.metadata.ram ?? 0,
						swap_mb: selectedPlan.value?.metadata.swap ?? 0,
						storage_mb: selectedPlan.value?.metadata.storage ?? 0,
					}
				: {
						cpu: lowestProduct.value.metadata.cpu ?? 0,
						memory_mb: lowestProduct.value.metadata.ram ?? 0,
						swap_mb: lowestProduct.value.metadata.swap ?? 0,
						storage_mb: lowestProduct.value.metadata.storage ?? 0,
					},
		),
	)
	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="selectedPlan">
						<ServersSpecs
							class="!flex-row justify-between"
							:ram="selectedPlan.metadata.ram ?? 0"
							:storage="selectedPlan.metadata.storage ?? 0"
							:cpus="selectedPlan.metadata.cpu ?? 0"
						/>
					</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>
