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

#[ cfg( feature = "test-support" ) ]
use std::collections::HashSet;

use crate::app::{ App, InvalidationScope, SurfaceTarget };
use super::AppData;

/// Apply a folded [`InvalidationScope`] from one message-batch iteration:
/// dirty the relevant cache(s) so the next draw rebuilds the view via
/// [`App::view`] / [`App::overlays`], and call `request_redraw` on each
/// affected surface so the run loop's "anything to draw" check picks it up.
///
/// `InvalidationScope::All` (the default returned by `App::invalidate_after`)
/// matches the pre-hook behaviour of broadcasting to every surface.
pub( super ) fn apply_invalidation<A: App>( data: &mut AppData<A>, scope: InvalidationScope )
{
	match scope
	{
		InvalidationScope::All =>
		{
			data.view_dirty     = true;
			data.overlays_dirty = true;
			data.main.request_redraw();
			for ss in data.overlays.values_mut()
			{
				ss.request_redraw();
			}
			for ( _, ss ) in data.secondaries.iter_mut()
			{
				ss.request_redraw();
			}
		}
		InvalidationScope::Only( targets ) =>
		{
			for t in targets
			{
				match t
				{
					SurfaceTarget::Main =>
					{
						data.view_dirty = true;
						data.main.request_redraw();
						// Lock extras mirror the main surface's backdrop, so
						// they follow its invalidations.
						for ( _, ss ) in data.secondaries.iter_mut()
						{
							ss.request_redraw();
						}
					}
					SurfaceTarget::Overlay( id ) =>
					{
						// `overlays()` returns a single Vec, so any
						// per-overlay change forces the whole list to be
						// re-queried. Coarser than per-overlay caching but
						// matches the existing API shape.
						data.overlays_dirty = true;
						if let Some( ss ) = data.overlays.get_mut( &id )
						{
							ss.request_redraw();
						}
					}
				}
			}
		}
	}
}

/// Pure overlay-id diff. Given the current set of live overlay ids and the
/// list the app just returned from [`crate::app::App::overlays`], compute
/// `( added, removed )`.
///
/// `added` preserves the order of `next` (so creation order is deterministic);
/// `removed` is unordered (driven by HashMap iteration in the caller).
///
/// Exposed only for `test_support`; gated behind that feature so it does not
/// warn as dead code in ordinary builds.
#[ cfg( feature = "test-support" ) ]
pub fn diff_overlay_ids(
	current: impl IntoIterator<Item = crate::app::OverlayId>,
	next:    &[ crate::app::OverlayId ],
) -> ( Vec<crate::app::OverlayId>, Vec<crate::app::OverlayId> )
{
	let current_set: HashSet<crate::app::OverlayId> = current.into_iter().collect();
	let next_set:    HashSet<crate::app::OverlayId> = next.iter().copied().collect();

	let added:   Vec<_> = next.iter().copied().filter( |id| !current_set.contains( id ) ).collect();
	let removed: Vec<_> = current_set.into_iter().filter( |id| !next_set.contains( id ) ).collect();
	( added, removed )
}
