use crate::SheetId;
use crate::engine::graph::DependencyGraph;
use crate::engine::named_range::NameScope;
use crate::engine::vertex::{VertexId, VertexKind};
use formualizer_common::{Coord as AbsCoord, ExcelError, ExcelErrorKind};

#[derive(Debug, Clone)]
pub struct SourceScalarEntry {
    pub name: String,
    pub vertex: VertexId,
    pub version: Option<u64>,
}

#[derive(Debug, Clone)]
pub struct SourceTableEntry {
    pub name: String,
    pub vertex: VertexId,
    pub version: Option<u64>,
}

impl DependencyGraph {
    fn next_source_coord(&mut self) -> AbsCoord {
        const COLS: u32 = 16_384;
        const SOURCE_ROW_OFFSET: u32 = 524_288;

        let seq = self.source_vertex_seq;
        self.source_vertex_seq = self.source_vertex_seq.wrapping_add(1);

        let row = (seq / COLS)
            .saturating_add(SOURCE_ROW_OFFSET)
            .min(0x000F_FFFF);
        let col = seq % COLS;
        AbsCoord::new(row, col)
    }

    fn allocate_source_vertex(&mut self) -> VertexId {
        let coord = self.next_source_coord();
        let sheet_id: SheetId = self.default_sheet_id;
        let vertex = self.store.allocate(coord, sheet_id, 0x01);
        self.edges.add_vertex(coord, vertex.0);
        self.store.set_kind(vertex, VertexKind::External);
        vertex
    }

    pub fn resolve_source_scalar_entry(&self, name: &str) -> Option<&SourceScalarEntry> {
        self.source_scalars.get(name)
    }

    pub fn resolve_source_table_entry(&self, name: &str) -> Option<&SourceTableEntry> {
        self.source_tables.get(name)
    }

    pub fn define_source_scalar(
        &mut self,
        name: &str,
        version: Option<u64>,
    ) -> Result<(), ExcelError> {
        if name.is_empty() {
            return Err(ExcelError::new(ExcelErrorKind::Name)
                .with_message("Source name cannot be empty".to_string()));
        }
        if self.source_scalars.contains_key(name) || self.source_tables.contains_key(name) {
            return Err(ExcelError::new(ExcelErrorKind::Name)
                .with_message(format!("Source already defined: {name}")));
        }

        let vertex = self.allocate_source_vertex();
        self.source_vertex_lookup.insert(vertex, name.to_string());
        self.mark_volatile(vertex, version.is_none());

        let entry = SourceScalarEntry {
            name: name.to_string(),
            vertex,
            version,
        };
        self.source_scalars.insert(name.to_string(), entry);
        self.resolve_pending_name_references(NameScope::Workbook, name);
        Ok(())
    }

    pub fn define_source_table(
        &mut self,
        name: &str,
        version: Option<u64>,
    ) -> Result<(), ExcelError> {
        if name.is_empty() {
            return Err(ExcelError::new(ExcelErrorKind::Name)
                .with_message("Source name cannot be empty".to_string()));
        }
        if self.source_tables.contains_key(name) || self.source_scalars.contains_key(name) {
            return Err(ExcelError::new(ExcelErrorKind::Name)
                .with_message(format!("Source already defined: {name}")));
        }

        let vertex = self.allocate_source_vertex();
        self.source_vertex_lookup.insert(vertex, name.to_string());
        self.mark_volatile(vertex, version.is_none());

        let entry = SourceTableEntry {
            name: name.to_string(),
            vertex,
            version,
        };
        self.source_tables.insert(name.to_string(), entry);
        Ok(())
    }

    pub fn set_source_scalar_version(
        &mut self,
        name: &str,
        version: Option<u64>,
    ) -> Result<(), ExcelError> {
        let vertex = {
            let entry = self.source_scalars.get_mut(name).ok_or_else(|| {
                ExcelError::new(ExcelErrorKind::Name)
                    .with_message(format!("Unknown source: {name}"))
            })?;

            if entry.version == version {
                return Ok(());
            }

            entry.version = version;
            entry.vertex
        };

        self.mark_volatile(vertex, version.is_none());
        self.mark_dirty(vertex);
        Ok(())
    }

    pub fn set_source_table_version(
        &mut self,
        name: &str,
        version: Option<u64>,
    ) -> Result<(), ExcelError> {
        let vertex = {
            let entry = self.source_tables.get_mut(name).ok_or_else(|| {
                ExcelError::new(ExcelErrorKind::Name)
                    .with_message(format!("Unknown source: {name}"))
            })?;

            if entry.version == version {
                return Ok(());
            }

            entry.version = version;
            entry.vertex
        };

        self.mark_volatile(vertex, version.is_none());
        self.mark_dirty(vertex);
        Ok(())
    }

    pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
        if let Some(s) = self.source_scalars.get(name) {
            self.mark_dirty(s.vertex);
            return Ok(());
        }
        if let Some(t) = self.source_tables.get(name) {
            self.mark_dirty(t.vertex);
            return Ok(());
        }
        Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown source: {name}")))
    }
}
