use super::*;

#[derive(ShaderType, Reflect, Debug, Clone, Copy, PartialEq)]
pub struct SurfaceUniform {
    pub shader_id: u32,
    pub flags: u32,
    pub alpha_cutoff: f32,
    pub abi_version: u32,
    pub params: [Vec4; SURFACE_PARAMETER_LANES],
    pub uv_transforms: [Vec4; SURFACE_TEXTURE_SLOTS],
}

impl Default for SurfaceUniform {
    fn default() -> Self {
        Self {
            shader_id: 0,
            flags: 0,
            alpha_cutoff: 0.5,
            abi_version: SURFACE_ABI_VERSION,
            params: [Vec4::ZERO; SURFACE_PARAMETER_LANES],
            uv_transforms: [Vec4::new(1.0, 1.0, 0.0, 0.0); SURFACE_TEXTURE_SLOTS],
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SurfaceExtensionKey {
    shader: AssetId<Shader>,
}

impl From<&SurfaceExtension> for SurfaceExtensionKey {
    fn from(extension: &SurfaceExtension) -> Self {
        Self {
            shader: extension.shader.id(),
        }
    }
}

#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default)]
#[bind_group_data(SurfaceExtensionKey)]
pub struct SurfaceExtension {
    #[uniform(100)]
    pub uniform: SurfaceUniform,
    #[texture(101)]
    #[sampler(102)]
    pub texture0: Option<Handle<Image>>,
    #[texture(103)]
    #[sampler(104)]
    pub texture1: Option<Handle<Image>>,
    #[texture(105)]
    #[sampler(106)]
    pub texture2: Option<Handle<Image>>,
    #[texture(107)]
    #[sampler(108)]
    pub texture3: Option<Handle<Image>>,
    #[texture(109)]
    #[sampler(110)]
    pub texture4: Option<Handle<Image>>,
    #[texture(111)]
    #[sampler(112)]
    pub texture5: Option<Handle<Image>>,
    #[texture(113)]
    #[sampler(114)]
    pub texture6: Option<Handle<Image>>,
    #[texture(115)]
    #[sampler(116)]
    pub texture7: Option<Handle<Image>>,
    #[reflect(ignore)]
    pub shader: Handle<Shader>,
}

impl SurfaceExtension {
    pub fn set_texture(&mut self, index: usize, handle: Option<Handle<Image>>) {
        match index {
            0 => self.texture0 = handle,
            1 => self.texture1 = handle,
            2 => self.texture2 = handle,
            3 => self.texture3 = handle,
            4 => self.texture4 = handle,
            5 => self.texture5 = handle,
            6 => self.texture6 = handle,
            7 => self.texture7 = handle,
            _ => {}
        }
    }
}

impl MaterialExtension for SurfaceExtension {
    fn fragment_shader() -> ShaderRef {
        DEFAULT_SURFACE_SHADER_PATH.into()
    }

    fn deferred_fragment_shader() -> ShaderRef {
        DEFAULT_SURFACE_SHADER_PATH.into()
    }

    fn specialize(
        _pipeline: &MaterialExtensionPipeline,
        descriptor: &mut RenderPipelineDescriptor,
        _layout: &MeshVertexBufferLayoutRef,
        key: MaterialExtensionKey<Self>,
    ) -> Result<(), SpecializedMeshPipelineError> {
        if let (AssetId::Uuid { uuid }, Some(fragment)) =
            (key.bind_group_data.shader, descriptor.fragment.as_mut())
        {
            // The evaluator fragment owns forward and deferred shading, but ordinary depth,
            // normal, and motion-vector prepasses must retain Bevy's standard prepass shader.
            // Substituting the evaluator there calls `deferred_output()` without a
            // `DEFERRED_PREPASS` output attachment and fails shader validation at runtime.
            if surface_evaluator_owns_fragment(&fragment.shader_defs) {
                fragment.shader = Handle::Uuid(uuid, Default::default());
            }
        }
        Ok(())
    }
}

pub(super) fn surface_evaluator_owns_fragment(shader_defs: &[ShaderDefVal]) -> bool {
    !shader_def_enabled(shader_defs, "PREPASS_PIPELINE")
        || shader_def_enabled(shader_defs, "DEFERRED_PREPASS")
}

pub(super) fn shader_def_enabled(shader_defs: &[ShaderDefVal], expected: &str) -> bool {
    shader_defs.iter().any(|shader_def| match shader_def {
        ShaderDefVal::Bool(name, enabled) => name == expected && *enabled,
        ShaderDefVal::Int(name, value) => name == expected && *value != 0,
        ShaderDefVal::UInt(name, value) => name == expected && *value != 0,
    })
}

pub type SurfaceMaterial = ExtendedMaterial<StandardMaterial, SurfaceExtension>;
