//! Daemon configuration read from `/etc/eydos/loginmanager.toml` (same file
//! the greeter uses).
//!
//! `[session]` — user-session command (post-auth fork target).
//! `[greeter]` — greeter command + user + VT (spawned by the daemon at boot).
//! `[splash]`  — optional pre-session splash binary that displays a logo on
//!               the user's VT while forge is starting up, breaking the
//!               DRM-master-vs-VT-switch chicken/egg (the splash signals
//!               `ready` immediately, then blocks on `VT_WAITACTIVE` until
//!               the daemon switches VT and finally execs the real session).
//!
//! Missing file or missing section → safe defaults. `--config <path>`
//! overrides the location.

use anyhow::Result;
use serde::Deserialize;
use std::path::{ Path, PathBuf };

pub const DEFAULT_PATH: &str = "/etc/eydos/loginmanager.toml";

#[ derive( Clone, Debug ) ]
pub struct DaemonConfig
{
	pub session: SessionEntry,
	pub greeter: GreeterEntry,
	pub splash:  SplashConfig,
}

#[ derive( Clone, Debug ) ]
pub struct SplashConfig
{
	pub enabled: bool,
	pub binary:  PathBuf,
	pub logo:    PathBuf,
}

#[ derive( Clone, Debug ) ]
pub struct SessionEntry
{
	pub command:     PathBuf,
	pub environment: Vec<( String, String )>,
}

#[ derive( Clone, Debug ) ]
pub struct GreeterEntry
{
	pub command:     PathBuf,
	pub user:        String,
	pub vt:          u32,
	pub environment: Vec<( String, String )>,
}

impl Default for DaemonConfig
{
	fn default() -> Self
	{
		DaemonConfig
		{
			session: SessionEntry
			{
				command:     PathBuf::from( "/bin/bash" ),
				environment: Vec::new(),
			},
			greeter: GreeterEntry
			{
				command:     PathBuf::from( "/usr/bin/loginmanager-greeter" ),
				user:        "_loginmanager".to_string(),
				vt:          7,
				environment: Vec::new(),
			},
			splash: SplashConfig
			{
				enabled: true,
				binary:  PathBuf::from( "/usr/bin/loginmanager-splash" ),
				logo:    PathBuf::from( "/usr/share/eydos-loginmanager/splash-logo.svg" ),
			},
		}
	}
}

#[ derive( Deserialize, Default ) ]
#[ serde( default ) ]
struct RawConfig
{
	session: RawSession,
	greeter: RawGreeter,
	splash:  RawSplash,
}

#[ derive( Deserialize, Default ) ]
#[ serde( default ) ]
struct RawSession
{
	default_command: Option<String>,
	environment:     Vec<( String, String )>,
}

#[ derive( Deserialize, Default ) ]
#[ serde( default ) ]
struct RawGreeter
{
	command:     Option<String>,
	user:        Option<String>,
	vt:          Option<u32>,
	environment: Vec<( String, String )>,
}

#[ derive( Deserialize, Default ) ]
#[ serde( default ) ]
struct RawSplash
{
	enabled: Option<bool>,
	binary:  Option<String>,
	logo:    Option<String>,
}

pub fn load( path: &Path ) -> Result<DaemonConfig>
{
	if !path.exists()
	{
		tracing::info!( "config: {} not found, using defaults", path.display() );
		let mut cfg = DaemonConfig::default();
		inject_keyboard_env( &mut cfg.session.environment );
		inject_keyboard_env( &mut cfg.greeter.environment );
		return Ok( cfg );
	}
	let raw_text = std::fs::read_to_string( path )?;
	let raw: RawConfig = toml::from_str( &raw_text )?;

	let defaults = DaemonConfig::default();

	let mut session = SessionEntry
	{
		command: raw.session.default_command
			.map( PathBuf::from )
			.unwrap_or( defaults.session.command ),
		environment: raw.session.environment,
	};

	let mut greeter = GreeterEntry
	{
		command: raw.greeter.command
			.map( PathBuf::from )
			.unwrap_or( defaults.greeter.command ),
		user: raw.greeter.user.unwrap_or( defaults.greeter.user ),
		vt:   raw.greeter.vt.unwrap_or( defaults.greeter.vt ),
		environment: raw.greeter.environment,
	};

	let splash = SplashConfig
	{
		enabled: raw.splash.enabled.unwrap_or( defaults.splash.enabled ),
		binary:  raw.splash.binary.map( PathBuf::from ).unwrap_or( defaults.splash.binary ),
		logo:    raw.splash.logo  .map( PathBuf::from ).unwrap_or( defaults.splash.logo   ),
	};

	inject_keyboard_env( &mut session.environment );
	inject_keyboard_env( &mut greeter.environment );

	Ok( DaemonConfig { session, greeter, splash } )
}

/// Honour `/etc/default/keyboard` the way gdm/sddm do: parse XKBLAYOUT,
/// XKBVARIANT, XKBMODEL and XKBOPTIONS and project them onto libxkbcommon's
/// `XKB_DEFAULT_*` env vars so forge picks them up. User-provided entries in
/// `loginmanager.toml` take precedence (we only insert keys not already set).
fn inject_keyboard_env( env: &mut Vec<( String, String )> )
{
	let path = Path::new( "/etc/default/keyboard" );
	let Ok( text ) = std::fs::read_to_string( path ) else { return; };

	let map = [
		( "XKBLAYOUT",  "XKB_DEFAULT_LAYOUT"  ),
		( "XKBVARIANT", "XKB_DEFAULT_VARIANT" ),
		( "XKBMODEL",   "XKB_DEFAULT_MODEL"   ),
		( "XKBOPTIONS", "XKB_DEFAULT_OPTIONS" ),
	];

	for line in text.lines()
	{
		let line = line.trim();
		if line.is_empty() || line.starts_with( '#' ) { continue; }
		let Some( ( key, value ) ) = line.split_once( '=' ) else { continue; };
		let value = value.trim().trim_matches( '"' ).trim_matches( '\'' );
		if value.is_empty() { continue; }
		for ( src, dst ) in &map
		{
			if key.trim() == *src && !env.iter().any( |( k, _ )| k == dst )
			{
				env.push( ( dst.to_string(), value.to_string() ) );
			}
		}
	}
}
