// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Settings client for the Crustace notification-channels interface
//! (`org.crustace.Notifications.Channels` on the notification daemon). Lists
//! every app's channels and lets the user block / unblock them; the daemon
//! persists the policy and suppresses notifications on blocked channels.

use std::collections::HashMap;

use zbus::blocking::{ Connection, Proxy };
use zbus::zvariant::OwnedValue;

const DEST:  &str = "org.freedesktop.Notifications";
const PATH:  &str = "/org/freedesktop/Notifications";
const IFACE: &str = "org.crustace.Notifications.Channels";

fn conn() -> Option<Connection>
{
	Connection::session().ok()
}

fn proxy( c: &Connection ) -> Option<Proxy<'_>>
{
	Proxy::new( c, DEST, PATH, IFACE ).ok()
}

#[ derive( Clone ) ]
pub struct ChannelInfo
{
	pub app_id:     String,
	pub channel_id: String,
	pub name:       String,
	pub importance: i32,
	pub blocked:    bool,
}

fn dict_str( d: &HashMap<String, OwnedValue>, key: &str ) -> String
{
	d.get( key ).and_then( | v | <&str>::try_from( &**v ).ok() ).map( str::to_string ).unwrap_or_default()
}

fn dict_i32( d: &HashMap<String, OwnedValue>, key: &str ) -> i32
{
	d.get( key ).and_then( | v | i32::try_from( &**v ).ok() ).unwrap_or( -1 )
}

fn dict_bool( d: &HashMap<String, OwnedValue>, key: &str ) -> bool
{
	d.get( key ).and_then( | v | bool::try_from( &**v ).ok() ).unwrap_or( false )
}

/// Every registered channel, across all apps, sorted by app then name. Empty
/// when the daemon is unreachable.
pub fn load() -> Vec<ChannelInfo>
{
	let Some( c ) = conn() else { return Vec::new(); };
	let Some( p ) = proxy( &c ) else { return Vec::new(); };
	let apps: Vec<String> = p.call( "ListApps", &() ).unwrap_or_default();
	let mut out = Vec::new();
	for app in apps
	{
		let chans: Vec<HashMap<String, OwnedValue>> = p.call( "ListChannels", &( app.as_str(), ) ).unwrap_or_default();
		for d in chans
		{
			out.push( ChannelInfo
			{
				app_id:     dict_str( &d, "app_id" ),
				channel_id: dict_str( &d, "channel_id" ),
				name:       dict_str( &d, "name" ),
				importance: dict_i32( &d, "importance" ),
				blocked:    dict_bool( &d, "blocked" ),
			} );
		}
	}
	out.sort_by( | a, b | ( &a.app_id, &a.name ).cmp( &( &b.app_id, &b.name ) ) );
	out
}

/// Block or unblock a channel. Blocking sets importance 0; unblocking resets to
/// the app default (importance -1).
pub fn set_blocked( app_id: &str, channel_id: &str, blocked: bool )
{
	let Some( c ) = conn() else { return; };
	let Some( p ) = proxy( &c ) else { return; };
	let importance: i32 = if blocked { 0 } else { -1 };
	let r: zbus::Result<()> = p.call( "SetChannelImportance", &( app_id, channel_id, importance ) );
	let _ = r;
}
