<script lang="ts">
import { _ } from "svelte-i18n";
import Meta from "$lib/components/meta/meta.svelte";
import {
	ButtplugClient,
	ButtplugWasmClientConnector,
	type ButtplugClientDevice,
	type OutputType,
	InputType,
	DeviceOutputValueConstructor,
} from "@sexy.pivoine.art/buttplug";
import type { ButtplugMessage } from "@sexy.pivoine.art/buttplug";
import Button from "$lib/components/ui/button/button.svelte";
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import DeviceCard from "$lib/components/device-card/device-card.svelte";
import RecordingSaveDialog from "./components/recording-save-dialog.svelte";
import DeviceMappingDialog from "./components/device-mapping-dialog.svelte";
import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types";
import { toast } from "svelte-sonner";

const client = new ButtplugClient("Sexy.Art");
let connected = $state(client.connected);
let scanning = $state(false);
let devices = $state<BluetoothDevice[]>([]);

// Recording state
let isRecording = $state(false);
let recordingStartTime = $state<number | null>(null);
let recordedEvents = $state<RecordedEvent[]>([]);
let showSaveDialog = $state(false);
let recordingDuration = $state(0);

// Playback state
let isPlaying = $state(false);
let playbackProgress = $state(0);
let playbackStartTime = $state<number | null>(null);
let playbackTimeoutId = $state<number | null>(null);
let currentEventIndex = $state(0);
let showMappingDialog = $state(false);
let deviceMappings = $state<Map<string, BluetoothDevice>>(new Map());

async function init() {
	const connector = new ButtplugWasmClientConnector();
	// await ButtplugWasmClientConnector.activateLogging("info");
	await client.connect(connector);
	client.on("deviceadded", onDeviceAdded);
	client.on("deviceremoved", (dev: ButtplugClientDevice) => {
		const idx = devices.findIndex((d) => d.info.index === dev.index);
		if (idx !== -1) devices.splice(idx, 1);
	});
	client.on("scanningfinished", () => (scanning = false));
	client.on("inputreading", handleInputReading);
	connected = client.connected;
}

async function startScanning() {
	await client.startScanning();
	scanning = true;
}

async function onDeviceAdded(dev: ButtplugClientDevice) {
	const device = convertDevice(dev);
	devices.push(device);

	// Try to read battery level — access through the reactive array so Svelte detects the mutation
	const idx = devices.length - 1;
	if (device.hasBattery) {
		try {
			devices[idx].batteryLevel = await dev.battery();
		} catch (e) {
			console.warn(`Failed to read battery for ${dev.name}:`, e);
		}
	}
}

function handleInputReading(msg: ButtplugMessage) {
	if (msg.InputReading === undefined) return;
	const reading = msg.InputReading;
	const device = devices.find((d) => d.info.index === reading.DeviceIndex);
	if (!device) return;

	if (reading.Reading[InputType.Battery] !== undefined) {
		device.batteryLevel = reading.Reading[InputType.Battery].Value;
	}
	device.lastSeen = new Date();
}

async function handleChange(
	device: BluetoothDevice,
	actuatorIdx: number,
	value: number,
) {
	const actuator = device.actuators[actuatorIdx];
	const feature = device.info.features.get(actuator.featureIndex);
	if (!feature) return;

	actuator.value = value;
	const outputType = actuator.outputType as OutputType;
	await feature.runOutput(new DeviceOutputValueConstructor(outputType).steps(value));

	// Capture event if recording
	if (isRecording && recordingStartTime) {
		captureEvent(device, actuatorIdx, value);
	}
}

function startRecording() {
	if (devices.length === 0) {
		return;
	}
	isRecording = true;
	recordingStartTime = performance.now();
	recordedEvents = [];
	recordingDuration = 0;
}

function stopRecording() {
	isRecording = false;
	if (recordedEvents.length > 0) {
		recordingDuration = recordedEvents[recordedEvents.length - 1].timestamp;
		showSaveDialog = true;
	}
}

function captureEvent(
	device: BluetoothDevice,
	actuatorIdx: number,
	value: number,
) {
	if (!recordingStartTime) return;

	const timestamp = performance.now() - recordingStartTime;
	const actuator = device.actuators[actuatorIdx];

	recordedEvents.push({
		timestamp,
		deviceIndex: device.info.index,
		deviceName: device.name,
		actuatorIndex: actuatorIdx,
		actuatorType: actuator.outputType,
		value: (value / actuator.maxSteps) * 100, // Normalize to 0-100
	});
}

async function handleStop(device: BluetoothDevice) {
	await device.info.stop();
	device.actuators.forEach((a) => (a.value = 0));
}

function convertDevice(device: ButtplugClientDevice): BluetoothDevice {
	const actuators: import("$lib/types").DeviceActuator[] = []; // eslint-disable-line @typescript-eslint/consistent-type-imports
	for (const [, feature] of device.features) {
		for (const outputType of feature.outputTypes) {
			actuators.push({
				featureIndex: feature.featureIndex,
				outputType,
				maxSteps: feature.outputMaxValue(outputType),
				descriptor: feature.featureDescriptor,
				value: 0,
			});
		}
	}

	return {
		id: String(device.index),
		name: device.name,
		actuators,
		batteryLevel: 0,
		hasBattery: device.hasInput(InputType.Battery),
		isConnected: true,
		lastSeen: new Date(),
		info: device,
	};
}

async function handleSaveRecording(data: {
	title: string;
	description: string;
	tags: string[];
}) {
	const deviceInfo: DeviceInfo[] = devices.map((d) => ({
		name: d.name,
		index: d.info.index,
		capabilities: d.actuators.map((a) => a.outputType),
	}));

	try {
		const response = await fetch("/api/sexy/recordings", {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
			},
			body: JSON.stringify({
				title: data.title,
				description: data.description,
				duration: recordingDuration,
				events: recordedEvents,
				device_info: deviceInfo,
				tags: data.tags,
				status: "draft",
			}),
		});

		if (!response.ok) {
			throw new Error(`HTTP error! status: ${response.status}`);
		}

		toast.success("Recording saved successfully!");
		showSaveDialog = false;
		recordedEvents = [];
		recordingDuration = 0;

		// Optionally navigate to dashboard
		// goto("/me?tab=recordings");
	} catch (error) {
		console.error("Failed to save recording:", error);
		toast.error("Failed to save recording. Please try again.");
	}
}

function handleCancelSave() {
	showSaveDialog = false;
	recordedEvents = [];
	recordingDuration = 0;
}

// Playback functions
function startPlayback() {
	if (!data.recording) {
		return;
	}

	if (devices.length === 0) {
		toast.error("Please connect devices before playing recording");
		return;
	}

	// Check if we need to map devices
	if (deviceMappings.size === 0 && data.recording.device_info.length > 0) {
		showMappingDialog = true;
		return;
	}

	// Start playback with existing mappings
	beginPlayback();
}

function beginPlayback() {
	isPlaying = true;
	playbackStartTime = performance.now();
	playbackProgress = 0;
	currentEventIndex = 0;
	scheduleNextEvent();
}

function handleMappingConfirm(mappings: Map<string, BluetoothDevice>) {
	deviceMappings = mappings;
	showMappingDialog = false;
	beginPlayback();
}

function handleMappingCancel() {
	showMappingDialog = false;
}

function stopPlayback() {
	isPlaying = false;
	if (playbackTimeoutId !== null) {
		clearTimeout(playbackTimeoutId);
		playbackTimeoutId = null;
	}
	playbackProgress = 0;
	currentEventIndex = 0;

	// Stop all devices
	devices.forEach((device) => handleStop(device));
}

function pausePlayback() {
	isPlaying = false;
	if (playbackTimeoutId !== null) {
		clearTimeout(playbackTimeoutId);
		playbackTimeoutId = null;
	}
}

function resumePlayback() {
	if (!data.recording) return;

	isPlaying = true;
	playbackStartTime = performance.now() - playbackProgress;
	scheduleNextEvent();
}

function scheduleNextEvent() {
	if (!data.recording || !isPlaying || !playbackStartTime) return;

	const events = data.recording.events;
	if (currentEventIndex >= events.length) {
		stopPlayback();
		toast.success("Playback finished");
		return;
	}

	const event = events[currentEventIndex];
	const currentTime = performance.now() - playbackStartTime;
	const delay = event.timestamp - currentTime;

	if (delay <= 0) {
		// Execute event immediately
		executeEvent(event);
		currentEventIndex++;
		scheduleNextEvent();
	} else {
		// Schedule event
		playbackTimeoutId = setTimeout(() => {
			executeEvent(event);
			currentEventIndex++;
			playbackProgress = event.timestamp;
			scheduleNextEvent();
		}, delay) as unknown as number;
	}
}

function executeEvent(event: RecordedEvent) {
	// Get mapped device
	const device = deviceMappings.get(event.deviceName);
	if (!device) {
		console.warn(`No device mapping for: ${event.deviceName}`);
		return;
	}

	// Find matching actuator by type
	const actuator = device.actuators.find(
		(a) => a.outputType === event.actuatorType,
	);
	if (!actuator) {
		console.warn(`Actuator type ${event.actuatorType} not found on ${device.name}`);
		return;
	}

	// Convert normalized value (0-100) back to device scale
	const deviceValue = Math.round((event.value / 100) * actuator.maxSteps);

	// Send command to device via feature
	const feature = device.info.features.get(actuator.featureIndex);
	if (feature) {
		const outputType = actuator.outputType as OutputType;
		feature.runOutput(new DeviceOutputValueConstructor(outputType).steps(deviceValue));
	}

	// Update UI
	actuator.value = deviceValue;
}

function seek(percentage: number) {
	if (!data.recording) return;

	const targetTime = (percentage / 100) * data.recording.duration;
	playbackProgress = targetTime;

	// Find the event index at this time
	currentEventIndex = data.recording.events.findIndex(e => e.timestamp >= targetTime);
	if (currentEventIndex === -1) {
		currentEventIndex = data.recording.events.length;
	}

	if (isPlaying) {
		if (playbackTimeoutId !== null) {
			clearTimeout(playbackTimeoutId);
		}
		playbackStartTime = performance.now() - targetTime;
		scheduleNextEvent();
	}
}

const { data } = $props();

onMount(() => {
	if (data.authStatus.authenticated) {
		init();
		return;
	}
	goto("/login");
});
</script>

<Meta title={$_("play.title")} description={$_("play.description")} />

<div
    class="relative min-h-screen bg-gradient-to-br from-background via-primary/5 to-accent/5 overflow-hidden"
>
    <!-- Global Plasma Background -->
    <div class="absolute inset-0 pointer-events-none">
        <div
            class="absolute top-40 left-1/4 w-80 h-80 bg-gradient-to-r from-primary/16 via-accent/20 to-primary/12 rounded-full blur-3xl animate-blob-slow"
        ></div>
        <div
            class="absolute bottom-40 right-1/4 w-96 h-96 bg-gradient-to-r from-accent/16 via-primary/20 to-accent/12 rounded-full blur-3xl animate-blob-slow animation-delay-5000"
        ></div>
        <div
            class="absolute top-1/3 right-1/3 w-64 h-64 bg-gradient-to-r from-primary/14 via-accent/18 to-primary/10 rounded-full blur-2xl animate-blob-reverse animation-delay-2500"
        ></div>
    </div>

    <div class="container mx-auto py-20 relative px-4">
        <div class="max-w-4xl mx-auto">
            <!-- Header -->
            <div class="text-center mb-12">
                <h1
                    class="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-primary via-accent to-primary bg-clip-text text-transparent"
                >
                    {$_("play.title")}
                </h1>
                <p class="text-lg text-muted-foreground mb-6">
                    {$_("play.description")}
                </p>
                <div class="flex justify-center gap-3 mb-10">
                    <Button
                        variant="outline"
                        size="sm"
                        href="/leaderboard"
                        class="border-primary/30 hover:bg-primary/10"
                    >
                        <span class="icon-[ri--trophy-line] w-4 h-4 mr-2"></span>
                        {$_("gamification.leaderboard")}
                    </Button>
                    <Button
                        variant="outline"
                        size="sm"
                        href="/me"
                        class="border-primary/30 hover:bg-primary/10"
                    >
                        <span class="icon-[ri--user-line] w-4 h-4 mr-2"></span>
                        {$_("common.my_profile")}
                    </Button>
                </div>
                <div class="flex justify-center gap-4 items-center">
                    <Button
                        size="lg"
                        disabled={!connected || scanning}
                        onclick={startScanning}
                        class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
                    >
                        {#if scanning}
                            <div
                                class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"
                            ></div>
                            {$_("play.scanning")}
                        {:else}
                            {$_("play.scan")}
                        {/if}
                    </Button>

                    {#if devices.length > 0 && !data.recording}
                        {#if !isRecording}
                            <Button
                                size="lg"
                                variant="outline"
                                onclick={startRecording}
                                class="cursor-pointer border-primary/30 hover:bg-primary/10"
                            >
                                <span class="icon-[ri--record-circle-line] w-5 h-5 mr-2"></span>
                                Start Recording
                            </Button>
                        {:else}
                            <Button
                                size="lg"
                                onclick={stopRecording}
                                class="cursor-pointer bg-red-500 hover:bg-red-600 text-white"
                            >
                                <span class="icon-[ri--stop-circle-fill] w-5 h-5 mr-2 animate-pulse"></span>
                                Stop Recording ({recordedEvents.length} events)
                            </Button>
                        {/if}
                    {/if}
                </div>
            </div>

            <!-- Playback Controls (only shown when recording is loaded) -->
            {#if data.recording}
                <div class="bg-card/50 border border-primary/20 rounded-lg p-6 backdrop-blur-sm">
                    <div class="mb-4">
                        <h2 class="text-xl font-semibold text-card-foreground mb-2">
                            {data.recording.title}
                        </h2>
                        {#if data.recording.description}
                            <p class="text-sm text-muted-foreground">
                                {data.recording.description}
                            </p>
                        {/if}
                    </div>

                    <!-- Progress Bar -->
                    <div class="mb-4">
                        <div class="flex items-center gap-3 mb-2">
                            <span class="text-sm text-muted-foreground min-w-[50px]">
                                {Math.floor(playbackProgress / 1000 / 60)}:{(Math.floor(playbackProgress / 1000) % 60).toString().padStart(2, '0')}
                            </span>
                            <div class="flex-1 h-2 bg-muted rounded-full overflow-hidden cursor-pointer relative"
                                 onclick={(e) => {
                                    const rect = e.currentTarget.getBoundingClientRect();
                                    const percentage = ((e.clientX - rect.left) / rect.width) * 100;
                                    seek(percentage);
                                 }}>
                                <div class="absolute inset-0 bg-gradient-to-r from-primary to-accent transition-all duration-150"
                                     style="width: {(playbackProgress / data.recording.duration) * 100}%"></div>
                            </div>
                            <span class="text-sm text-muted-foreground min-w-[50px] text-right">
                                {Math.floor(data.recording.duration / 1000 / 60)}:{(Math.floor(data.recording.duration / 1000) % 60).toString().padStart(2, '0')}
                            </span>
                        </div>
                    </div>

                    <!-- Playback Buttons -->
                    <div class="flex gap-2 justify-center">
                        <Button
                            size="lg"
                            variant="outline"
                            onclick={stopPlayback}
                            disabled={!isPlaying && playbackProgress === 0}
                            class="cursor-pointer border-primary/30 hover:bg-primary/10"
                        >
                            <span class="icon-[ri--stop-fill] w-5 h-5"></span>
                        </Button>
                        {#if !isPlaying}
                            <Button
                                size="lg"
                                onclick={playbackProgress > 0 ? resumePlayback : startPlayback}
                                disabled={devices.length === 0}
                                class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 min-w-[120px]"
                            >
                                <span class="icon-[ri--play-fill] w-5 h-5 mr-2"></span>
                                {playbackProgress > 0 ? 'Resume' : 'Play'}
                            </Button>
                        {:else}
                            <Button
                                size="lg"
                                onclick={pausePlayback}
                                class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 min-w-[120px]"
                            >
                                <span class="icon-[ri--pause-fill] w-5 h-5 mr-2"></span>
                                Pause
                            </Button>
                        {/if}
                    </div>

                    <!-- Recording Info -->
                    <div class="mt-4 pt-4 border-t border-border/50 grid grid-cols-3 gap-4 text-center">
                        <div>
                            <p class="text-xs text-muted-foreground">Events</p>
                            <p class="text-sm font-medium">{data.recording.events.length}</p>
                        </div>
                        <div>
                            <p class="text-xs text-muted-foreground">Devices</p>
                            <p class="text-sm font-medium">{data.recording.device_info.length}</p>
                        </div>
                        <div>
                            <p class="text-xs text-muted-foreground">Status</p>
                            <p class="text-sm font-medium capitalize">{data.recording.status}</p>
                        </div>
                    </div>
                </div>
            {/if}
        </div>
    </div>
    <div class="container mx-auto px-4 py-12">
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
            {#if devices}
                {#each devices as device (device.name)}
                    <DeviceCard
                        {device}
                        onChange={(scalarIndex, val) => handleChange(device, scalarIndex, val)}
                        onStop={() => handleStop(device)}
                    />
                {/each}
            {/if}
        </div>

        {#if devices?.length === 0}
            <div class="text-center py-12">
                <p class="text-muted-foreground text-lg mb-4">
                    {$_("play.no_results")}
                </p>
            </div>
        {/if}
    </div>

    <!-- Recording Save Dialog -->
    <RecordingSaveDialog
        open={showSaveDialog}
        events={recordedEvents}
        deviceInfo={devices.map((d) => ({
            name: d.name,
            index: d.info.index,
            capabilities: d.actuators.map((a) => a.outputType),
        }))}
        duration={recordingDuration}
        onSave={handleSaveRecording}
        onCancel={handleCancelSave}
    />

    <!-- Device Mapping Dialog -->
    {#if data.recording}
        <DeviceMappingDialog
            open={showMappingDialog}
            recordedDevices={data.recording.device_info}
            connectedDevices={devices}
            onConfirm={handleMappingConfirm}
            onCancel={handleMappingCancel}
        />
    {/if}
</div>
