#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-render-pipeline -- GPU-agnostic Render Graph (DAG-based)
use std::collections::HashMap;

#[derive(Debug)]
pub enum RenderError { CyclicGraph(String), PassFailed(String), ResourceMissing(String) }
impl std::fmt::Display for RenderError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::CyclicGraph(e) => write!(f, "Cycle: {e}"), Self::PassFailed(e) => write!(f, "Pass: {e}"), Self::ResourceMissing(e) => write!(f, "Missing: {e}") }
    }
}
impl std::error::Error for RenderError {}

#[derive(Debug, Clone)]
pub enum GpuBackend { Vulkan, Metal, WebGpu, OpenGl, Software }

#[derive(Debug, Clone)]
pub struct RenderPass { pub name: String, pub inputs: Vec<String>, pub outputs: Vec<String>, pub shader: String }

#[derive(Debug, Clone)]
pub struct RenderResource { pub name: String, pub width: u32, pub height: u32, pub format: String, pub size_bytes: u64 }

pub struct RenderGraph {
    passes: Vec<RenderPass>,
    resources: HashMap<String, RenderResource>,
    execution_order: Vec<usize>,
    backend: GpuBackend,
}

impl RenderGraph {
    pub fn new(backend: GpuBackend) -> Self {
        Self { passes: Vec::new(), resources: HashMap::new(), execution_order: Vec::new(), backend }
    }

    pub fn add_pass(&mut self, pass: RenderPass) -> usize {
        let idx = self.passes.len();
        self.passes.push(pass);
        idx
    }

    pub fn add_resource(&mut self, res: RenderResource) { self.resources.insert(res.name.clone(), res); }

    /// Topological sort of render passes (DAG)
    pub fn compile(&mut self) -> Result<Vec<String>, RenderError> {
        let n = self.passes.len();
        let mut in_degree = vec![0usize; n];
        let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];

        // Build dependency graph: if pass B reads output of pass A, A → B
        for (i, pass_b) in self.passes.iter().enumerate() {
            for input in &pass_b.inputs {
                for (j, pass_a) in self.passes.iter().enumerate() {
                    if i != j && pass_a.outputs.contains(input) {
                        adj[j].push(i);
                        in_degree[i] += 1;
                    }
                }
            }
        }

        // Kahn's algorithm
        let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
        let mut order = Vec::new();

        while let Some(node) = queue.pop() {
            order.push(node);
            for &next in &adj[node] {
                in_degree[next] -= 1;
                if in_degree[next] == 0 { queue.push(next); }
            }
        }

        if order.len() != n { return Err(RenderError::CyclicGraph("Dependency cycle detected".into())); }

        self.execution_order = order.clone();
        Ok(order.iter().map(|&i| self.passes[i].name.clone()).collect())
    }

    /// Validate all pass inputs are available
    pub fn validate(&self) -> Result<(), RenderError> {
        let mut available: Vec<String> = self.resources.keys().cloned().collect();
        for &idx in &self.execution_order {
            let pass = &self.passes[idx];
            for input in &pass.inputs {
                if !available.contains(input) {
                    return Err(RenderError::ResourceMissing(format!("Pass '{}' needs '{}' but not produced yet", pass.name, input)));
                }
            }
            for output in &pass.outputs { if !available.contains(output) { available.push(output.clone()); } }
        }
        Ok(())
    }

    /// Estimate VRAM usage
    pub fn estimate_vram(&self) -> u64 { self.resources.values().map(|r| r.size_bytes).sum() }
    pub fn pass_count(&self) -> usize { self.passes.len() }
    pub fn backend(&self) -> &GpuBackend { &self.backend }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_compile_order() {
        let mut g = RenderGraph::new(GpuBackend::WebGpu);
        g.add_pass(RenderPass { name: "shadow".into(), inputs: vec![], outputs: vec!["shadow_map".into()], shader: "shadow.wgsl".into() });
        g.add_pass(RenderPass { name: "gbuffer".into(), inputs: vec![], outputs: vec!["albedo".into(), "normal".into()], shader: "gbuffer.wgsl".into() });
        g.add_pass(RenderPass { name: "lighting".into(), inputs: vec!["albedo".into(), "normal".into(), "shadow_map".into()], outputs: vec!["hdr".into()], shader: "lighting.wgsl".into() });
        g.add_pass(RenderPass { name: "tonemap".into(), inputs: vec!["hdr".into()], outputs: vec!["final".into()], shader: "tonemap.wgsl".into() });
        let order = g.compile().unwrap();
        let light_idx = order.iter().position(|n| n == "lighting").unwrap();
        let shadow_idx = order.iter().position(|n| n == "shadow").unwrap();
        let gbuf_idx = order.iter().position(|n| n == "gbuffer").unwrap();
        assert!(shadow_idx < light_idx);
        assert!(gbuf_idx < light_idx);
    }
    #[test]
    fn test_cycle_detection() {
        let mut g = RenderGraph::new(GpuBackend::Vulkan);
        g.add_pass(RenderPass { name: "a".into(), inputs: vec!["y".into()], outputs: vec!["x".into()], shader: "a".into() });
        g.add_pass(RenderPass { name: "b".into(), inputs: vec!["x".into()], outputs: vec!["y".into()], shader: "b".into() });
        assert!(g.compile().is_err());
    }
}
