<script lang="ts">
	import * as Plot from '@observablehq/plot';
	import * as race from '$lib/race';
	import * as accel from '$lib/racelib/accel';
	import type { Attachment } from 'svelte/attachments';
	import CalcInfo from '../CalcInfo.svelte';

	let rawPower = $state(1200);
	let rawGuts = $state(1200);
	let rawWit = $state(1200);
	let powerBonus = $state(0);
	let gutsBonus = $state(0);
	let witBonus = $state(0);
	let style = $state(race.RunningStyle.FrontRunner);
	let surfApt = $state(race.AptitudeLevel.A);
	let styleApt = $state(race.AptitudeLevel.A);
	let mood = $state(race.Mood.Normal);
	let isCareer = $state(false);

	let raceLen = $state(2000);
	let surface = $state(race.Surface.Turf);
	let conditions = $state(race.GroundConditions.Firm);
	let slope = $state(0);

	let rawSkills: accel.Skill[] = $state([]);
	let skillToAdd: accel.Skill = $state({ accel: 0.2, dur: 1.2 });
	const quick = [
		{
			name: 'GW+TTL',
			skills: [
				{ accel: 0.2, dur: 3 },
				{ accel: 0.4, dur: 1.2 },
			],
		},
		{
			name: 'GW+EL',
			skills: [
				{ accel: 0.2, dur: 3 },
				{ accel: 0.2, dur: 1.2 },
			],
		},
		{ name: 'Clear', skills: [] },
	];
	function addSkill() {
		rawSkills.push(skillToAdd);
		skillToAdd = { accel: 0.2, dur: 1.2 };
	}
	function removeSkill(i: number) {
		rawSkills.splice(i, 1);
	}
	function setSkillList(l: accel.Skill[]) {
		rawSkills = l;
	}

	const phases = $derived(race.phases(raceLen));

	const basePower = $derived(race.baseStat(mood, rawPower));
	const adjPower = $derived(race.adjustedStat(race.Stat.Power, basePower, isCareer, surface, conditions));
	const power = $derived(race.finalStat(adjPower, powerBonus));
	const baseGuts = $derived(race.baseStat(mood, rawGuts));
	const adjGuts = $derived(race.adjustedStat(race.Stat.Guts, baseGuts, isCareer));
	const guts = $derived(race.finalStat(adjGuts, gutsBonus));
	const baseWit = $derived(race.baseStat(mood, rawWit));
	const adjWit = $derived(race.adjustedStat(race.Stat.Wit, baseWit, isCareer, styleApt));
	const wit = $derived(race.finalStat(adjWit, witBonus));

	const dashAccel = $derived(
		race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, true),
	);
	const postAccel = $derived(
		race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, false),
	);
	const minSpeed = $derived(race.minSpeed(raceLen, guts));
	const baseSpeed = $derived(race.baseSpeed(raceLen));
	const dashTarget = $derived(baseSpeed * 0.85);
	const hillMod: [number, number] = $derived.by(() => {
		if (slope > 0) {
			const m = race.uphillMod(power, slope);
			return [m, m];
		}
		if (slope < 0) {
			return [0, race.downhillAccelMod(slope)];
		}
		return [0, 0];
	});
	const earlyTarget = $derived(race.sectionSpeed(raceLen, baseWit, wit, style, race.Phase.EarlyRace));
	const speeds: [number, number] = $derived([earlyTarget[0] + hillMod[0], earlyTarget[1] + hillMod[1]]);

	const startInstant: accel.Instant = { t: 0, x: 0, v: 3 };
	function doSeggsToIt(targetSpeed: number, skills: accel.Skill[]) {
		const startDash = accel.up(startInstant, dashTarget, dashAccel, skills);
		const postStart = { ...last(startDash.segments).end, v: minSpeed };
		const post = accel.up(postStart, targetSpeed, postAccel, startDash.skills);
		const r = [...startDash.segments, ...post.segments];
		return r;
	}
	function last<T>(l: T[]): T {
		return l[l.length - 1];
	}
	const skills = $derived(rawSkills.map((s) => ({ ...s, dur: race.skillDuration(s.dur, raceLen) })));
	const seggs = $derived([doSeggsToIt(speeds[0], skills), doSeggsToIt(speeds[1], skills)]);
	const unskilledSeggs = $derived([doSeggsToIt(speeds[0], []), doSeggsToIt(speeds[1], [])]);

	const distance = $derived([last(seggs[0]).end.x, last(seggs[1]).end.x]);
	const time = $derived([last(seggs[0]).end.t, last(seggs[1]).end.t]);
	const midTime = $derived([
		time[0] + (phases[race.Phase.EarlyRace] - distance[0]) / speeds[1],
		time[1] + (phases[race.Phase.EarlyRace] - distance[1]) / speeds[0],
	]);
	function gainLengths(fast: accel.Instant, slow: accel.Instant): number {
		const m = fast.v * (slow.t - fast.t) + fast.x - slow.x;
		return m / race.HORSE_LENGTH;
	}
	const gain = $derived([
		gainLengths(last(seggs[0]).end, last(unskilledSeggs[0]).end),
		gainLengths(last(seggs[1]).end, last(unskilledSeggs[1]).end),
	]);

	let width = $state(1);
	let height = $state(1);
	const plotLines = $derived([
		{ t: 0, v: 3, x: 0, label: 'Slowest Speed' },
		...seggs[0].map((t) => ({ t: t.end.t, v: t.end.v, x: t.end.x, label: 'Slowest Speed' })),
		{ t: midTime[1], v: speeds[0], label: 'Slowest Speed' },
		{ t: 0, v: 3, x: 0, label: 'Fastest Speed' },
		...seggs[1].map((t) => ({ t: t.end.t, v: t.end.v, x: t.end.x, label: 'Fastest Speed' })),
		{ t: midTime[1], v: speeds[1], label: 'Fastest Speed' },
		{ t: 0, v: 3, x: 0, label: 'Without Skills' },
		...unskilledSeggs[1].map((t) => ({ t: t.end.t, v: t.end.v, x: t.end.x, label: 'Without Skills' })),
		{ t: midTime[1], v: speeds[1], label: 'Without Skills' },
	]);
	const makeChart: Attachment = (el) => {
		$effect(() => {
			el?.firstChild?.remove();
			el?.append(
				Plot.plot({
					width,
					height,
					clip: true,
					x: {
						domain: [0, midTime[1]],
						label: 'Time (s)',
						line: true,
					},
					y: {
						domain: [0, 23],
						label: 'Speed (m/s)',
						line: true,
					},
					color: { legend: true },
					marks: [Plot.frame(), Plot.line(plotLines, { x: 't', y: 'v', stroke: 'label', strokeWidth: 3 })],
				}),
			);
		});
	};
</script>

<h1 class="text-4xl">Gate Calculator</h1>
<form class="mx-auto my-4 grid max-w-4xl grid-cols-1 gap-4 rounded-md p-4 shadow-sm md:grid-cols-5">
	<div class="grid rounded-md text-center shadow-md ring md:col-span-5 md:grid-cols-5">
		<div class="m-4">
			<label for="powerStat">Power</label>
			<input type="number" id="powerStat" bind:value={rawPower} class="w-full" />
		</div>
		<div class="m-4">
			<label for="gutsStat">Guts</label>
			<input type="number" id="gutsStat" bind:value={rawGuts} class="w-full" />
		</div>
		<div class="m-4">
			<label for="witStat">Wit</label>
			<input type="number" id="witStat" bind:value={rawWit} class="w-full" />
		</div>
		<div class="m-4">
			<label for="style">Style</label>
			<select id="style" required bind:value={style} class="w-full">
				{#each race.RUNNING_STYLES as [name, style] (style)}
					<option value={style}>{name}</option>
				{/each}
			</select>
		</div>
		<div class="m-4">
			<label for="mood">Mood</label>
			<select id="mood" required bind:value={mood} class="w-full">
				{#each race.MOODS as mood (mood)}
					<option value={mood}>{race.Mood[mood]}</option>
				{/each}
			</select>
		</div>
		<div class="m-4">
			<label for="powerBonus">Power Greens</label>
			<input type="number" id="powerBonus" bind:value={powerBonus} class="w-full" />
		</div>
		<div class="m-4">
			<label for="gutsBonus">Guts Greens</label>
			<input type="number" id="gutsBonus" bind:value={gutsBonus} class="w-full" />
		</div>
		<div class="m-4">
			<label for="witBonus">Wit Greens</label>
			<input type="number" id="witBonus" bind:value={witBonus} class="w-full" />
		</div>
		<div class="m-4">
			<label for="surfaceApt">{race.Surface[surface]}</label>
			<select id="surfaceApt" required bind:value={surfApt} class="w-full">
				{#each race.APTITUDE_LEVELS as apt (apt)}
					<option value={apt}>{race.AptitudeLevel[apt]}</option>
				{/each}
			</select>
		</div>
		<div class="m-4">
			<label for="styleApt">Style</label>
			<select id="styleApt" required bind:value={styleApt} class="w-full">
				{#each race.APTITUDE_LEVELS as apt (apt)}
					<option value={apt}>{race.AptitudeLevel[apt]}</option>
				{/each}
			</select>
		</div>
	</div>
	<div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-3">
		<input
			class="col-span-4 m-4 w-full justify-self-center-safe"
			type="range"
			id="raceLen"
			min="1000"
			max="3600"
			step="100"
			bind:value={raceLen}
		/>
		<span class="col-span-2 my-auto mb-4 pl-2 text-sm">{raceLen}m</span>
		<select id="surface" bind:value={surface} class="col-span-2 m-4">
			<option value={race.Surface.Turf}>Turf</option>
			<option value={race.Surface.Dirt}>Dirt</option>
		</select>
		<select id="conditions" bind:value={conditions} class="col-span-2 m-4">
			{#each race.GROUND_CONDITONS as cond (cond)}
				<option value={cond}>{race.GroundConditions[cond]}</option>
			{/each}
		</select>
		<select id="slope" bind:value={slope} class="col-span-2 m-4">
			{#each [2, 1.5, 1, 0, -1, -1.5, -2] as per (per)}
				<option value={per}>{per > 0 ? `Uphill +${per}` : per < 0 ? `Downhill ${per}` : 'Flat'}</option>
			{/each}
		</select>
	</div>
	<div class="mb-auto flex w-full max-w-4xl flex-col rounded-md text-center shadow-md ring md:col-span-2">
		<div class="mx-4 mt-2 flex flex-row gap-2">
			<span class="w-24 flex-1">Duration</span>
			<span class="w-24 flex-1">Accel</span>
			<span class="w-8 flex-none"><!-- filler to align other columns --></span>
		</div>
		{#each rawSkills as skill, i (i)}
			<div class="mx-4 mt-2 flex flex-row gap-2">
				<span class="my-auto w-24 flex-1">{skill.dur}s</span>
				<span class="my-auto w-24 flex-1">{skill.accel}</span>
				<button
					type="button"
					class="my-auto h-8 w-8 flex-none rounded-full bg-mist-300 text-2xl hover:cursor-pointer dark:bg-mist-900"
					onclick={() => removeSkill(i)}>&times;</button
				>
			</div>
		{/each}
		<div class="mx-4 mt-2 flex flex-row gap-2">
			<input type="number" class="my-auto w-24 flex-1" min="0.9" step="0.3" bind:value={skillToAdd.dur} />
			<input type="number" class="my-auto w-24 flex-1" min="0" step="0.05" bind:value={skillToAdd.accel} />
			<button
				type="button"
				class="my-auto h-8 w-8 flex-none rounded-full bg-mist-300 text-2xl hover:cursor-pointer dark:bg-mist-900"
				onclick={addSkill}>+</button
			>
		</div>
		<div class="mt-1 flex flex-row">
			<span class="my-auto h-0 flex-1 border"></span>
			<span class="mx-2 flex-none">Quick Setup</span>
			<span class="my-auto h-0 flex-1 border"></span>
		</div>
		<div class="m-2 flex flex-row place-content-center-safe gap-2">
			{#each quick as q (q.name)}
				<button
					type="button"
					class="rounded-md bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900"
					onclick={() => setSkillList(q.skills)}>{q.name}</button
				>
			{/each}
		</div>
	</div>
</form>

{#snippet info(name: string, unit: string, val: number, val2?: number)}
	<CalcInfo title={name} class="max-w-72 flex-1">
		{#if val2 == null}
			<span class="block text-xl">{val.toFixed(3)} {unit}</span>
		{:else}
			<span class="block text-xl">{val.toFixed(3)} &ndash; {val2.toFixed(3)} {unit}</span>
		{/if}
	</CalcInfo>
{/snippet}

<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
	{@render info('Base Acceleration', 'm/s²', postAccel)}
	{@render info('Target Speed', 'm/s', speeds[0], speeds[1])}
</div>
<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
	{@render info('Accel Time', 's', time[0], time[1])}
	{@render info('Accel Distance', 'm', distance[0], distance[1])}
	{@render info('Time to Mid-Race', 's', midTime[0], midTime[1])}
</div>
<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
	{@render info('Skill Gain', 'L', gain[0], gain[1])}
</div>
<div class="mx-auto mb-12 h-72 max-w-4xl" bind:clientWidth={width} bind:clientHeight={height}>
	<div role="img" {@attach makeChart}>the chart seems to have didn't</div>
</div>

{#snippet seggsTable(name: string, s: accel.Travel[])}
	<table class="table-fixed border">
		<caption class="text-xl">{name}</caption>
		<thead>
			<tr class="py-2">
				<th class="px-2" scope="col">Time</th>
				<th class="px-2" scope="col">Accel</th>
				<th class="px-2" scope="col">Distance</th>
				<th class="px-2" scope="col">Speed</th>
			</tr>
		</thead>
		<tbody>
			{#each s as { start, end, a } (start.t)}
				<tr class="even:bg-mist-300 dark:even:bg-mist-900">
					<td class="px-4 text-center">{end.t.toFixed(3)}</td>
					<td class="px-4 text-center">{a.toFixed(3)}</td>
					<td class="px-4 text-center">{end.x.toFixed(3)}</td>
					<td class="px-4 text-center">{end.v.toFixed(3)}</td>
				</tr>
			{/each}
		</tbody>
	</table>
{/snippet}
<div class="mx-auto mt-8 grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-2">
	{@render seggsTable(slope < 0 ? 'Fastest Speed (Downhill)' : 'Fastest Speed', seggs[1])}
	{@render seggsTable('Slowest Speed', seggs[0])}
</div>
<div class="mx-auto mt-12 w-full max-w-4xl border-t pt-4 md:mt-8">
	<p>
		This calculator integrates acceleration analytically for efficiency, but the game uses a finite step approximation. The
		difference has some implications:
	</p>
	<ul class="ml-4 list-disc">
		<li>Late starts are dramatically more punishing in the game, as acceleration is not applied for the first frame.</li>
		<li>
			Start dash always completes a single frame at the dash target speed. This calculator finds the exact moment start dash
			target speed would be reached and continues integrating from there, leading to a slightly higher speed.
		</li>
	</ul>
</div>
