//! [TSM.ID].[11031972] — Platform X Ecosystem
//! Mark-Sweep collector with tri-color marking

use crate::{GcColor, GcError, GcConfig, Result};

struct GcObject {
    data: Vec<u8>,
    color: GcColor,
    refs: Vec<usize>,
    generation: u8,
    ref_count: u32,
}

pub struct MarkSweepCollector {
    objects: Vec<Option<GcObject>>,
    roots: Vec<usize>,
    free_slots: Vec<usize>,
    collections: u64,
    bytes_freed: u64,
    config: GcConfig,
}

impl MarkSweepCollector {
    pub fn new(config: GcConfig) -> Self {
        Self {
            objects: Vec::with_capacity(config.heap_size / 64),
            roots: Vec::new(), free_slots: Vec::new(),
            collections: 0, bytes_freed: 0, config,
        }
    }

    pub fn allocate(&mut self, size: usize) -> Result<usize> {
        let total: usize = self.objects.iter().filter_map(|o| o.as_ref()).map(|o| o.data.len()).sum();
        if total + size > self.config.heap_size {
            self.collect()?;
            let after: usize = self.objects.iter().filter_map(|o| o.as_ref()).map(|o| o.data.len()).sum();
            if after + size > self.config.heap_size { return Err(GcError::AllocationFailed); }
        }
        let obj = GcObject { data: vec![0u8; size], color: GcColor::White, refs: Vec::new(), generation: 0, ref_count: 0 };
        if let Some(slot) = self.free_slots.pop() {
            self.objects[slot] = Some(obj);
            Ok(slot)
        } else {
            let idx = self.objects.len();
            self.objects.push(Some(obj));
            Ok(idx)
        }
    }

    pub fn add_root(&mut self, idx: usize) { self.roots.push(idx); }
    pub fn add_reference(&mut self, from: usize, to: usize) -> Result<()> {
        if from >= self.objects.len() || to >= self.objects.len() { return Err(GcError::InvalidReference); }
        if let Some(obj) = &mut self.objects[from] { obj.refs.push(to); }
        if let Some(obj) = &mut self.objects[to] { obj.ref_count += 1; }
        Ok(())
    }

    pub fn collect(&mut self) -> Result<usize> {
        // Phase 1: Mark all white
        for obj in self.objects.iter_mut().flatten() { obj.color = GcColor::White; }
        // Phase 2: Mark roots gray
        let roots = self.roots.clone();
        for &r in &roots {
            if let Some(obj) = self.objects.get_mut(r).and_then(|o| o.as_mut()) { obj.color = GcColor::Gray; }
        }
        // Phase 3: Process gray objects
        let mut changed = true;
        while changed {
            changed = false;
            let len = self.objects.len();
            for i in 0..len {
                let refs = if let Some(Some(obj)) = self.objects.get(i) {
                    if obj.color == GcColor::Gray { obj.refs.clone() } else { continue; }
                } else { continue; };
                if let Some(obj) = self.objects[i].as_mut() { obj.color = GcColor::Black; }
                for r in refs {
                    if let Some(obj) = self.objects.get_mut(r).and_then(|o| o.as_mut()) {
                        if obj.color == GcColor::White { obj.color = GcColor::Gray; changed = true; }
                    }
                }
            }
        }
        // Phase 4: Sweep white objects
        let mut freed = 0usize;
        for i in 0..self.objects.len() {
            let is_white = self.objects[i].as_ref().map(|o| o.color == GcColor::White).unwrap_or(false);
            if is_white {
                if let Some(obj) = self.objects[i].take() {
                    freed += obj.data.len();
                    self.free_slots.push(i);
                }
            }
        }
        self.collections += 1;
        self.bytes_freed += freed as u64;
        Ok(freed)
    }

    pub fn stats(&self) -> (u64, u64, usize) {
        let alive = self.objects.iter().filter(|o| o.is_some()).count();
        (self.collections, self.bytes_freed, alive)
    }

    pub fn promote_survivors(&mut self) {
        for obj in self.objects.iter_mut().flatten() {
            if obj.color == GcColor::Black && obj.generation < 255 {
                obj.generation += 1;
            }
        }
    }

    pub fn object_generation(&self, idx: usize) -> Result<u8> {
        self.objects.get(idx)
            .and_then(|o| o.as_ref())
            .map(|o| o.generation)
            .ok_or(GcError::InvalidReference)
    }
}
