use super::*;

#[derive(Resource, Default)]
pub struct SurfaceMaterialCache {
    pub(super) handles: HashMap<MaterialRef, Handle<SurfaceMaterial>>,
    pub(super) standard_handles: HashMap<MaterialRef, Handle<StandardMaterial>>,
    pub(super) revisions: HashMap<MaterialRef, u64>,
    pub(super) standard_only: HashMap<MaterialRef, u64>,
    pub(super) instance_bases: HashMap<MaterialRef, MaterialRef>,
    pub(super) failed_revisions: HashMap<MaterialRef, u64>,
    pub(super) disk_generation: u64,
}

impl SurfaceMaterialCache {
    /// Invalidates disk-backed project materials after an explicit save or watcher event.
    /// Interactive edits use `LiveMaterialDocumentOverlay`, so synchronization never needs to
    /// stat or reparse authored files for every bound entity on every frame.
    pub fn invalidate_disk_documents(&mut self) {
        self.disk_generation = self.disk_generation.wrapping_add(1);
        self.failed_revisions.clear();
    }

    pub(super) fn disk_generation(&self) -> u64 {
        self.disk_generation
    }

    pub(super) fn revision_for_handles(
        &self,
        standard: Option<&MeshMaterial3d<StandardMaterial>>,
        surface: Option<&MeshMaterial3d<SurfaceMaterial>>,
    ) -> u64 {
        if let Some(handle) = standard {
            if let Some((reference, _)) = self
                .standard_handles
                .iter()
                .find(|(_, cached)| cached.id() == handle.0.id())
            {
                return self
                    .standard_only
                    .get(reference)
                    .copied()
                    .unwrap_or_default();
            }
        }
        if let Some(handle) = surface {
            if let Some((reference, _)) = self
                .handles
                .iter()
                .find(|(_, cached)| cached.id() == handle.0.id())
            {
                return self.revisions.get(reference).copied().unwrap_or_default();
            }
        }
        0
    }
}

#[derive(Resource, Default)]
pub(super) struct MaterialPropertyBlockCache {
    pub(super) entries: HashMap<(Entity, String), PropertyBlockCacheEntry>,
}

pub(super) enum PropertyBlockHandle {
    Standard(Handle<StandardMaterial>),
    Surface(Handle<SurfaceMaterial>),
}

pub(super) struct PropertyBlockCacheEntry {
    pub(super) revision: u64,
    pub(super) handle: PropertyBlockHandle,
}

#[derive(Resource, Default, Debug, Clone)]
pub struct DefaultGridMaterialHandles {
    pub surface: Option<Handle<SurfaceMaterial>>,
    pub emergency_standard: Option<Handle<StandardMaterial>>,
}

pub(super) enum BuiltRendererMaterial {
    Standard(Box<StandardMaterial>),
    Surface(Box<SurfaceMaterial>),
}

#[derive(Resource, Default, Debug, Clone)]
pub struct SurfaceDiagnostics(pub Vec<String>);

pub(super) fn record_diagnostic(diagnostics: &mut Vec<String>, message: String) {
    if diagnostics.iter().any(|existing| existing == &message) {
        return;
    }
    const MAX_DIAGNOSTICS: usize = 256;
    if diagnostics.len() == MAX_DIAGNOSTICS {
        diagnostics.remove(0);
    }
    diagnostics.push(message);
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SurfaceEvaluatorRecord {
    pub shader_id: u32,
    pub evaluator_source: String,
    pub revision: u64,
    pub alpha_mode: shared::MaterialAlphaMode,
}

/// Active, validated evaluator generation consumed by both raster and Solari render worlds.
#[derive(Resource, ExtractResource, Default, Debug, Clone)]
pub struct SurfaceEvaluatorRegistry {
    pub evaluators: HashMap<u32, SurfaceEvaluatorRecord>,
}

pub struct SurfaceMaterialPlugin;

impl Plugin for SurfaceMaterialPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(MaterialPlugin::<SurfaceMaterial>::default())
            .add_plugins(MaterialPlugin::<TerrainLayerMaterial>::default())
            .init_resource::<SurfaceMaterialCache>()
            .init_resource::<LiveMaterialDocumentOverlay>()
            .init_resource::<TerrainLayerMaterialCache>()
            .init_resource::<SurfaceDiagnostics>()
            .init_resource::<SurfaceEvaluatorRegistry>()
            .init_resource::<DefaultGridMaterialHandles>()
            .init_resource::<MaterialPropertyBlockCache>()
            .init_resource::<shared::ProjectContentDefaults>()
            .add_systems(
                Update,
                (
                    (
                        sync_surface_material_bindings,
                        apply_material_property_blocks,
                    )
                        .chain(),
                    sync_terrain_layer_material_bindings,
                ),
            );
    }
}
