import { ce, MorphComponent, MorphFeature } from 'morphux';
import { Main } from './main';
import {
	createProgress,
	formatUptime,
	ServiceState,
	setProgressState,
	setStatusState,
	StatusType,
} from './utils';

export class DashboardUnity {
	private _Main: Main;

	container: HTMLDivElement = document.querySelector('.ntsh_dashboard-unity');

	processStatus: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-processstatus'
	);
	processInfo: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-processinfo'
	);
	restartButton: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-restart'
	);

	uptimeInfo: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-uptime'
	);

	webSocketStatus: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-websocketstatus'
	);
	webSocketInfo: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-websocketinfo'
	);

	zedStreamStatus: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-zedstreamstatus'
	);
	zedStreamInfo: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-zedstreaminfo'
	);

	zedStreamFps: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-zedstreamfps'
	);
	zedStreamPath: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-zedstreampath'
	);

	timelineWatching: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-timeline-watching'
	);
	timelineStanding: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-timeline-standing'
	);
	timelineProgress: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-timeline-progress'
	);

	parametersTable: HTMLTableElement = document.querySelector(
		'.ntsh_dashboard-unity-parameters'
	);

	errorContainer: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-error'
	);
	errorText: HTMLDivElement = document.querySelector(
		'.ntsh_dashboard-unity-errortext'
	);

	constructor(Main: Main) {
		this._Main = Main;

		this.registerListeners();
	}

	private runnerError: string;
	updateRunnerState(state: UnityRunnerStatus) {
		// ----------- Process -----------
		if (state.state != 'RUNNING') {
			state.startTime = -1;

			this.restartButton.style.display = 'none';
		} else {
			this.restartButton.style.display = 'flex';
		}

		setStatusState(
			this.processStatus,
			{
				RUNNING: 'green',
				STOPPED: 'red',
				STARTING: 'yellow',
				PROBLEM: 'red',
			}[state.state] as StatusType
		);
		this.processInfo.innerText = state.message ?? '';

		// ----------- Uptime -----------
		const uptimeSeconds =
			state.startTime == -1 ? -1 : (Date.now() - state.startTime) / 1000;
		this.uptimeInfo.innerText = formatUptime(uptimeSeconds);

		// ----------- Error -----------
		if ((state?.error ?? '').trim().length > 0)
			this.runnerError = state.error;
		else this.runnerError = null;
		this.updateError();

		this._Main.Logs.setUnityLogs(state.output.current);
	}

	private webSocketError: string;
	updateWebSocketState(state: UnityWebSocketStatus) {
		// ----------- WebSocket -----------
		setStatusState(
			this.webSocketStatus,
			{
				CONNECTING: 'yellow',
				CONNECTED: 'green',
				DISCONNECTED: 'gray',
				FAILED: 'red',
			}[state.state] as StatusType
		);
		this.webSocketInfo.innerText = state.message ?? '';

		// ----------- ZED Stream -----------
		setStatusState(
			this.zedStreamStatus,
			state.parameters.zedReady ? 'green' : 'red'
		);
		this.zedStreamInfo.innerText = state.parameters.zedReady
			? `Connected to ${state.parameters.zedPath}`
			: 'Not ready';
		this.zedStreamFps.innerText =
			state.parameters.zedFPS == '-' ? '' : state.parameters.zedFPS;

		// ----------- Timeline -----------
		this.timelineWatching.innerText = state.parameters.timelineWatching
			? 'Yes'
			: 'No';
		this.timelineStanding.innerText = state.parameters.timelineStanding
			? 'Yes'
			: 'No';
		setProgressState(
			this.timelineProgress,
			state.parameters.timelineProgress
		);

		// ----------- Parameters -----------
		this.renderParameterSliders(state.parameters.parameters);

		// ----------- Error -----------
		if ((state?.error ?? '').trim().length > 0)
			this.webSocketError = state.error;
		else this.webSocketError = null;
		this.updateError();
	}

	private renderParameterSliders(parameters: UnityParameters['parameters']) {
		if (parameters.length === 0) return;

		const existingSliders = this.parametersTable.querySelectorAll(
			'.ntsh_dashboard-unity-parameter-row'
		);

		if (existingSliders.length !== parameters.length) {
			this.parametersTable.innerHTML = '';

			parameters.forEach((param) => {
				const row = ce('tr', 'ntsh_dashboard-unity-parameter-row');

				const nameCell = ce('td');
				nameCell.appendChild(
					ce('div', 'mux_text', null, param.sliderName)
				);
				row.appendChild(nameCell);

				const progressCell = ce('td', 'no-service');
				progressCell.appendChild(createProgress(param.outputValue));
				row.appendChild(progressCell);

				const sliderCell = ce('td', 'only-service');
				const sliderProgress = createProgress(param.outputValue);
				const sliderValue: HTMLDivElement =
					sliderProgress.querySelector('.ntsh_progress-value');
				sliderValue.classList.add('mux_resizer');
				sliderCell.appendChild(sliderProgress);

				const resizer = new MorphComponent.Resizer({
					existingContainer: sliderValue,
					direction: 'right',
					relative: true,
					min: 0,
					max: () => sliderProgress.clientWidth,
				});
				let lastPercentage: number = -1;
				resizer.on('resized', (size) => {
					const percentage =
						Math.round((size / sliderProgress.clientWidth) * 100) /
						100;
					if (percentage === lastPercentage) return;
					lastPercentage = percentage;

					this._Main.socket.emit(
						'unityWebSocket',
						'parameterValue',
						param.sliderIndex,
						percentage
					);
					setProgressState(sliderProgress, percentage);
				});

				row.appendChild(sliderCell);

				this.parametersTable.appendChild(row);
			});
		} else {
			existingSliders.forEach((row, index) => {
				const value = parameters[index].outputValue;

				const progressElement: HTMLDivElement = row.querySelector(
					'.no-service .ntsh_progress'
				);
				setProgressState(progressElement, value);

				const sliderElement: HTMLDivElement = row.querySelector(
					'.only-service .ntsh_progress'
				);
				if (sliderElement.querySelector('.mux_resizer-moving') == null)
					setProgressState(sliderElement, value);
			});
		}
	}

	private updateError() {
		const errors: string[] = [];
		if (this.runnerError != null) errors.push(this.runnerError);
		if (this.webSocketError != null) errors.push(this.webSocketError);

		if (errors.length > 0) {
			this.errorText.innerText = errors.join('\n');
			this.errorContainer.style.display = 'block';
		} else {
			this.errorContainer.style.display = 'none';
			this.errorText.innerText = '';
		}
	}

	registerListeners() {
		this._Main.socket.on('unityRunnerState', (state: UnityRunnerStatus) =>
			this.updateRunnerState(state)
		);
		this._Main.socket.on(
			'unityWebSocketState',
			(state: UnityWebSocketStatus) => this.updateWebSocketState(state)
		);

		this.restartButton.addEventListener('click', async () => {
			this.executeCommand(
				'restart',
				'Are you sure you want to restart the Unity Runner process?'
			);
		});
	}

	private async executeCommand(command: string, message: string) {
		const confirmed = await MorphFeature.Confirm({
			title: 'Are you sure?',
			message,
		});
		if (!confirmed) return;

		MorphFeature.Loader({
			active: true,
			message: `Requesting Unity Runner ${command}...`,
		});
		this._Main.socket.emit(
			'unityRunner',
			command,
			(response: { succeed: boolean; message?: string }) => {
				MorphFeature.Loader({ active: false });

				if (!response.succeed)
					return MorphFeature.Alert({
						title: 'Error',
						message: response.message,
					});

				MorphFeature.Notification({
					level: 'success',
					message: `Unity Runner is ${command}ing...`,
				});
			}
		);
	}
}

interface UnityRunnerStatus {
	state: 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM';
	message?: string;
	error?: string;

	startTime: number;
	output: { current: string[]; last: string[] };
}

interface UnityWebSocketStatus {
	state: ServiceState;
	message?: string;
	error?: string;

	parameters: UnityParameters;
}

interface UnityParameters {
	timelineWatching: boolean;
	timelineStanding: boolean;
	timelineProgress: number;
	zedPath: string;
	zedReady: boolean;
	zedFPS: string;
	parameters: UnitySocketMessageHeartbeat['heartbeat']['dataSliders'];
}

interface UnitySocketMessageBase {
	type: string;
	timestamp: number;
}
interface UnitySocketMessageHeartbeat extends UnitySocketMessageBase {
	type: 'heartbeat_data';
	heartbeat: {
		dataSliders: {
			sliderIndex: number;
			sliderName: string;
			outputValue: number;
		}[];
		dataTimeline: {
			isStanding: boolean;
			isWatching: boolean;
			timelineProgress: number;
		};
		zedCamera: {
			cameraFPS: string;
			isZedReady: boolean;
			streamInputIP: string;
			streamInputPort: number;
			zedGrabError: number;
		};
	};
}
