<script lang="ts">
	/*
	 * Conventions (apply to every .svelte file under features/):
	 *   - $state variables: camelCase, no prefix.
	 *   - $derived: camelCase.
	 *   - Component refs: camelCase + Ref.
	 *   - Event handlers: handleXxx.
	 *   - Prop callbacks: onXxx.
	 *   - HTML IDs: kebab-case, prefixed with a component-specific short code
	 *     (e.g. "cp-..." for ControlPanel) so IDs stay globally unique.
	 */
	import { onMount } from 'svelte';
	import {
		Button,
		Dropdown,
		DropdownItem,
		DropdownMenu,
		DropdownToggle,
		FormGroup,
		Icon,
		Input,
		InputGroup,
		InputGroupText,
		Label,
	} from '@sveltestrap/sveltestrap';
	import { CollapsibleCard, SelectSearchable, SpoilerGroup, addToast } from '$ui';
	import { pointsApi } from '$api';
	import {
		DEFAULT_FLIGHT_PARAMETERS,
		MAX_LAUNCH_LATITUDE,
		PROFILE_IDENTIFIERS,
		clampLaunchLatitude,
		wrapLongitude,
		toFixedNumber,
		type FlightParameters,
		type ProfileIdentifier,
		type SavedPoint,
	} from '$domain';
	import { workspacesStore, getActiveWorkspace } from '$features/workspaces';
	import { t } from '$i18n';
	import { pointsStore } from './pointsStore';
	import PointEditor from './PointEditor.svelte';
	import CurveEditor from './CurveEditor.svelte';

	interface Props {
		onSelectOnMapClick?: () => void;
	}
	let { onSelectOnMapClick = () => {} }: Props = $props();

	let pointEditorRef: PointEditor | null = $state(null);
	let curveEditorRef: CurveEditor | null = $state(null);

	let active = $derived(getActiveWorkspace($workspacesStore));
	let params = $derived<FlightParameters>(active?.flightParameters ?? DEFAULT_FLIGHT_PARAMETERS);
	let ascentProfile = $state('standard');
	let descentProfile = $state('standard');
	let selectedPointId = $derived(params.start_point ?? -1);

	let currentPoint = $derived($pointsStore.find((p) => p.id === selectedPointId) ?? null);
	let isPointDirty = $derived.by(() => {
		if (!currentPoint) return false;
		return (
			params.launch_latitude.toFixed(6) !== currentPoint.lat.toFixed(6) ||
			params.launch_longitude.toFixed(6) !== currentPoint.lon.toFixed(6) ||
			params.launch_altitude.toFixed(2) !== currentPoint.alt.toFixed(2)
		);
	});

	onMount(async () => {
		if ($pointsStore.length === 0) {
			try {
				pointsStore.set(await pointsApi.list());
			} catch (err: unknown) {
				addToast({ header: $t('common.error'), body: (err as Error).message, color: 'danger' });
			}
		}
	});

	function patchActive(patch: Partial<FlightParameters>) {
		if (!active) return;
		// Single choke point for launch latitude: typing it, clicking the map
		// (updateLaunchPosition) and picking a saved point all land here, so one
		// clamp covers every path.
		const safe = { ...patch };
		if (safe.launch_latitude !== undefined) {
			safe.launch_latitude = clampLaunchLatitude(safe.launch_latitude);
		}
		if (safe.launch_longitude !== undefined) {
			safe.launch_longitude = wrapLongitude(safe.launch_longitude);
		}
		workspacesStore.setFlightParameters(active.id, { ...active.flightParameters, ...safe });
	}

	function handlePointSelection(newPointId: number | null) {
		if (!active) return;
		if (newPointId == null || newPointId === -1) {
			patchActive({ start_point: -1 });
			return;
		}
		const point = $pointsStore.find((p) => p.id === newPointId);
		if (!point) return;
		patchActive({
			start_point: point.id,
			launch_latitude: point.lat,
			launch_longitude: point.lon,
			launch_altitude: point.alt,
		});
	}

	async function handleSaveCurrentPoint() {
		if (!currentPoint) {
			pointEditorRef?.open(
				{
					id: 0,
					name: `New Point ${new Date().toLocaleString()}`,
					lat: params.launch_latitude,
					lon: params.launch_longitude,
					alt: params.launch_altitude,
				},
				false,
			);
			return;
		}
		try {
			const saved = await pointsApi.update({
				...currentPoint,
				lat: params.launch_latitude,
				lon: params.launch_longitude,
				alt: params.launch_altitude,
			});
			pointsStore.update((list) => list.map((p) => (p.id === saved.id ? saved : p)));
			addToast({ header: $t('common.success'), body: saved.name, color: 'success' });
		} catch (err: unknown) {
			addToast({ header: $t('common.error'), body: (err as Error).message, color: 'danger' });
		}
	}

	async function handleRun() {
		if (!active) return;
		try {
			await workspacesStore.run(active.id);
			addToast({
				header: $t('forecast.success'),
				body: $t('forecast.successBody'),
				color: 'success',
			});
		} catch (err: unknown) {
			addToast({
				header: $t('forecast.error'),
				body: $t('forecast.errorBody', { error: (err as Error).message }),
				color: 'danger',
			});
		}
	}

	export function updateLaunchPosition(lat: number, lng: number) {
		patchActive({
			launch_latitude: toFixedNumber(lat, 6),
			launch_longitude: toFixedNumber(lng, 6),
		});
	}
</script>

<CollapsibleCard title={$t('conditions.title')}>
	{#if !active}
		<div class="text-muted small">{$t('workspaces.empty')}</div>
	{:else}
		<div class="d-flex gap-2">
			<FormGroup class="flex-fill w-50" spacing="mb-2">
				<Label for="cp-start-time" class="form-label">{$t('conditions.startTime')}</Label>
				<Input
					type="time"
					id="cp-start-time"
					class="form-control-sm"
					step="1"
					value={active.launchTime}
					oninput={(e) =>
						workspacesStore.patch(active!.id, {
							launchTime: (e.currentTarget as HTMLInputElement).value,
						})} />
			</FormGroup>
			<FormGroup class="flex-fill w-50" spacing="mb-2">
				<Label for="cp-start-date" class="form-label">{$t('conditions.startDate')}</Label>
				<Input
					type="date"
					id="cp-start-date"
					class="form-control-sm"
					value={active.launchDate}
					oninput={(e) =>
						workspacesStore.patch(active!.id, {
							launchDate: (e.currentTarget as HTMLInputElement).value,
						})} />
			</FormGroup>
		</div>

		<FormGroup spacing="mb-2">
			<Label for="cp-flight-profile" class="form-label">{$t('conditions.flightProfile')}</Label>
			<InputGroup size="sm">
				<Input
					type="select"
					id="cp-flight-profile"
					value={params.profile}
					onchange={(e) =>
						patchActive({
							profile: (e.currentTarget as HTMLSelectElement).value as ProfileIdentifier,
						})}>
					{#each PROFILE_IDENTIFIERS as id}
						<option value={id}>{$t(`profile.${id}`)}</option>
					{/each}
				</Input>
			</InputGroup>
		</FormGroup>

		<FormGroup spacing="mb-2">
			<Label for="cp-start-point" class="form-label">{$t('conditions.startPoint')}</Label>
			<InputGroup size="sm">
				<SelectSearchable
					style="min-height: calc(1.5em + .5rem + 2px); padding: .25rem .5rem; font-size: .875rem;"
					id="cp-start-point"
					selected={selectedPointId}
					onChange={handlePointSelection}
					options={$pointsStore.map((p) => ({
						value: p.id,
						label: `${p.name}${p.id === selectedPointId && isPointDirty ? ` (${$t('scenario.modified')})` : ''}`,
					}))}
					placeholder={$t('conditions.pointPlaceholder')}
					searchPlaceholder={$t('conditions.pointSearchPlaceholder')}
					clearable={true} />
				<Button color="secondary" size="sm" onclick={() => pointEditorRef?.open(null, true)}>
					<Icon name="journal-bookmark-fill" />
				</Button>
			</InputGroup>
		</FormGroup>

		<FormGroup spacing="mb-2">
			<Label class="form-label">{$t('conditions.latLng')}</Label>
			<InputGroup size="sm">
				<Input
					type="number"
					step="0.000001"
					min={-MAX_LAUNCH_LATITUDE}
					max={MAX_LAUNCH_LATITUDE}
					value={params.launch_latitude}
					oninput={(e) =>
						patchActive({
							launch_latitude: parseFloat((e.currentTarget as HTMLInputElement).value),
						})} />
				<InputGroupText>/</InputGroupText>
				<Input
					type="number"
					step="0.000001"
					value={params.launch_longitude}
					oninput={(e) =>
						patchActive({
							launch_longitude: parseFloat((e.currentTarget as HTMLInputElement).value),
						})} />
				<Button color="secondary" size="sm" onclick={onSelectOnMapClick}>
					<Icon name="geo-alt-fill" />
				</Button>
			</InputGroup>
		</FormGroup>

		<div class="d-flex mb-2">
			<Button
				color="primary"
				class="flex-fill"
				size="sm"
				onclick={handleSaveCurrentPoint}
				disabled={!isPointDirty && selectedPointId !== -1}>
				{$t('conditions.save')}
				<Icon name="floppy2-fill" class="ms-1" />
			</Button>
		</div>

		<div class="d-flex gap-2">
			<FormGroup class="flex-fill w-50" spacing="mb-2">
				<Label class="form-label">{$t('conditions.launchAlt')}</Label>
				<Input
					type="number"
					class="form-control-sm"
					value={params.launch_altitude}
					oninput={(e) =>
						patchActive({
							launch_altitude: parseFloat((e.currentTarget as HTMLInputElement).value),
						})} />
			</FormGroup>
			<FormGroup class="flex-fill w-50" spacing="mb-2">
				<Label class="form-label">{$t('conditions.burstAlt')}</Label>
				<Input
					type="number"
					class="form-control-sm"
					value={params.burst_altitude}
					oninput={(e) =>
						patchActive({
							burst_altitude: parseFloat((e.currentTarget as HTMLInputElement).value),
						})} />
			</FormGroup>
		</div>

		{#if params.profile !== 'custom_profile'}
			<div class="mb-2 d-flex gap-2">
				<FormGroup class="flex-fill w-50" spacing="mb-2">
					<Label class="form-label">{$t('conditions.ascentRate')}</Label>
					<Input
						type="number"
						class="form-control-sm"
						value={params.ascent_rate}
						oninput={(e) =>
							patchActive({
								ascent_rate: parseFloat((e.currentTarget as HTMLInputElement).value),
							})} />
				</FormGroup>
				<FormGroup class="flex-fill w-50" spacing="mb-2">
					<Label class="form-label">{$t('conditions.descentRate')}</Label>
					<Input
						type="number"
						class="form-control-sm"
						value={params.descent_rate}
						oninput={(e) =>
							patchActive({
								descent_rate: parseFloat((e.currentTarget as HTMLInputElement).value),
							})} />
				</FormGroup>
			</div>
		{:else}
			<SpoilerGroup label={$t('conditions.profileEdit')} class="mb-2">
				<Label class="form-label mb-0">{$t('conditions.ascentStage')}</Label>
				<div class="d-flex gap-2 mb-0">
					<Input type="radio" bind:group={ascentProfile} value="none" label={$t('conditions.stageNone')} />
					<Input type="radio" bind:group={ascentProfile} value="standard" label={$t('conditions.stageStandard')} />
					<Input type="radio" bind:group={ascentProfile} value="custom" label={$t('conditions.stageCustom')} />
				</div>
				<Label class="form-label mb-0">{$t('conditions.descentStage')}</Label>
				<div class="d-flex gap-2 mb-0">
					<Input type="radio" bind:group={descentProfile} value="none" label={$t('conditions.stageNone')} />
					<Input type="radio" bind:group={descentProfile} value="standard" label={$t('conditions.stageStandard')} />
					<Input type="radio" bind:group={descentProfile} value="custom" label={$t('conditions.stageCustom')} />
				</div>
				<Button size="sm" color="secondary" onclick={() => curveEditorRef?.openModal()} class="w-100">
					{$t('conditions.openCurveEditor')}
					<Icon name="graph-up-arrow" />
				</Button>
			</SpoilerGroup>
		{/if}

		<div class="d-flex">
			<Button class="flex-fill" size="sm" color="primary" onclick={handleRun}>
				{$t('conditions.run')}
			</Button>
		</div>
	{/if}
</CollapsibleCard>

<CurveEditor bind:this={curveEditorRef} showTable={true} editor={false} />
<PointEditor
	bind:this={pointEditorRef}
	onSelectPoint={(p: SavedPoint | null) => handlePointSelection(p?.id ?? -1)} />
