use serde::{Deserialize, Serialize};
use std::collections::HashSet;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Scope {
    All,
    Subset(Vec<String>),
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProgramSignature {
    entries: Vec<Option<Scope>>,
}

impl ProgramSignature {
    #[inline]
    pub(crate) fn ensure(&mut self, index: usize) {
        if self.entries.len() <= index {
            self.entries.resize(index + 1, None);
        }
        if self.entries[index].is_none() {
            self.entries[index] = Some(Scope::Subset(Vec::new()));
        }
    }

    #[inline]
    pub(crate) fn mark_all(&mut self, index: usize) {
        self.ensure(index);
        if let Some(entry) = self.entries.get_mut(index) {
            *entry = Some(Scope::All);
        }
    }

    #[inline]
    pub(crate) fn pop(&mut self, index: usize, columns: &[&str]) {
        self.ensure(index);
        let Some(Some(entry)) = self.entries.get_mut(index) else {
            return;
        };

        let mut seen: HashSet<String> = HashSet::new();
        let mut merged = match entry {
            Scope::All => return,
            Scope::Subset(existing) => existing
                .iter()
                .filter(|name| seen.insert((*name).clone()))
                .cloned()
                .collect::<Vec<_>>(),
        };
        merged.extend(
            columns
                .iter()
                .map(|name| (*name).to_string())
                .filter(|name| seen.insert(name.clone())),
        );
        *entry = Scope::Subset(merged);
    }

    #[inline]
    pub fn columns_for(&self, index: usize) -> Option<Scope> {
        self.entries.get(index).cloned().flatten()
    }

    #[inline]
    pub fn entries(&self) -> Vec<(usize, Vec<String>)> {
        self.entries
            .iter()
            .enumerate()
            .filter_map(|(ix, scope)| match scope {
                Some(Scope::Subset(columns)) => Some((ix, columns.clone())),
                Some(Scope::All) | None => None,
            })
            .collect()
    }
}
