use crate::app::App;
use crate::cli::Cli;
use crate::consts::APP_LOCK_FILE_NAME;
use crate::event::{AppEvent, CreateAppEventSchedulerEventLoopExt};
use clap::Parser;
use std::io::Write;
use std::process::ExitCode;
use tracing_subscriber::EnvFilter;
use ui::{Acceleration, UiConfig, UiContext, UiEvent, UiSetupResult};
use winit::event_loop::EventLoop;

pub(crate) use graphite_desktop_ui as ui;
pub(crate) use graphite_desktop_wrapper as wrapper;

mod app;
mod cli;
mod dirs;
mod event;
mod gpu_context;
mod persist;
mod preferences;
mod render;
mod socket;
mod window;

pub(crate) mod consts;

pub fn start() -> ExitCode {
	tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();

	let ui_context = match UiContext::setup() {
		UiSetupResult::Ready(context) => context,
		UiSetupResult::Helper(code) => return code,
		UiSetupResult::Failed => {
			tracing::error!("Failed to set up the UI runtime");
			return ExitCode::FAILURE;
		}
	};

	let cli = Cli::parse();

	let Ok(lock_file) = std::fs::OpenOptions::new()
		.read(true)
		.write(true)
		.create(true)
		.truncate(true)
		.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))
	else {
		tracing::error!("Failed to open lock file.");
		return ExitCode::FAILURE;
	};
	let mut lock = fd_lock::RwLock::new(lock_file);
	let lock = match lock.try_write() {
		Ok(mut guard) => {
			tracing::info!("Acquired application lock");
			let _ = guard.set_len(0);
			let _ = write!(guard, "{}", std::process::id());
			let _ = guard.sync_all();
			guard
		}
		Err(_) => {
			tracing::error!("Another instance is already running, Exiting.");
			if !cli.files.is_empty()
				&& let Err(error) = socket::send(socket::Message::OpenFiles(cli.files))
			{
				tracing::error!("Failed to send socket message to running instance: {}", error);
				return ExitCode::FAILURE;
			}
			return ExitCode::SUCCESS;
		}
	};

	dirs::clear_dir(&ui::temp_dir_root());

	// TODO: Eventually remove this cleanup code for the old "browser" CEF directory
	dirs::delete_old_cef_browser_directory();

	let mut prefs = preferences::read();

	// Must be called before event loop initialization or native window integrations will break
	App::init();

	let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());

	let event_loop = EventLoop::new().unwrap();
	let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel();
	let app_event_scheduler = event_loop.create_app_event_scheduler(app_event_sender);

	let _socket_handle = socket::start(app_event_scheduler.clone());

	if cli.disable_ui_acceleration {
		prefs.disable_ui_acceleration = true;
	}
	if prefs.disable_ui_acceleration {
		println!("UI acceleration is disabled");
	}

	let acceleration = if prefs.disable_ui_acceleration { Acceleration::Disabled } else { Acceleration::Auto };
	let ui_context = match ui_context.start(UiConfig { acceleration }) {
		Ok(context) => context,
		Err(error) => {
			tracing::error!("Failed to start the UI runtime: {error}");
			return ExitCode::FAILURE;
		}
	};
	let ui = match ui_context.instance(&wgpu_context.device, &wgpu_context.queue) {
		Ok(ui) => ui,
		Err(error) => {
			tracing::error!("Failed to start the UI: {error}");
			return ExitCode::FAILURE;
		}
	};
	tracing::info!("UI runtime started successfully");

	{
		let ui = ui.clone();
		let scheduler = app_event_scheduler.clone();
		let spawned = std::thread::Builder::new().name("ui-events".to_string()).spawn(move || {
			while let Some(event) = ui.recv() {
				match event {
					UiEvent::Ready => scheduler.schedule(AppEvent::WebCommunicationInitialized),
					UiEvent::Frame(texture) => scheduler.schedule(AppEvent::UiUpdate(texture)),
					UiEvent::Cursor(cursor) => scheduler.schedule(AppEvent::CursorChange(cursor)),
					UiEvent::Message(message) => match wrapper::deserialize_editor_message(&message) {
						Some(message) => scheduler.schedule(AppEvent::DesktopWrapperMessage(message)),
						None => tracing::error!("Failed to deserialize web message"),
					},
					UiEvent::Failure(error) => {
						tracing::error!("UI failure: {error}");
						scheduler.schedule(AppEvent::UiCrashed);
					}
					UiEvent::Crashed => scheduler.schedule(AppEvent::UiCrashed),
				}
			}
		});
		if let Err(error) = spawned {
			tracing::error!("Failed to spawn the UI event bridge thread: {error}");
			return ExitCode::FAILURE;
		}
	}

	let app = App::new(ui.clone(), wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);

	let exit_reason = app.run(event_loop);

	// ui needs to be shutdown before restarting
	ui.shutdown();

	// If exiting due to a UI acceleration failure, update preferences to disable it for next launch
	if matches!(exit_reason, app::ExitReason::UiAccelerationFailure) {
		tracing::error!("Disabling UI acceleration");
		preferences::modify(|prefs| {
			prefs.disable_ui_acceleration = true;
		});
	}

	// Explicitly drop the instance lock
	drop(lock);

	match exit_reason {
		app::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {
			tracing::info!("Restarting application");
			let mut command = std::process::Command::new(std::env::current_exe().unwrap());
			#[cfg(target_family = "unix")]
			let _ = std::os::unix::process::CommandExt::exec(&mut command);
			#[cfg(target_family = "unix")]
			tracing::error!("Failed to restart application");
			#[cfg(not(target_family = "unix"))]
			let _ = command.spawn();
		}
		_ => {}
	}

	ExitCode::SUCCESS
}
