// UCXL Core Data Structures

use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;

/// Represents the temporal axis in a UCXL address.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TemporalAxis {
    /// Present ("#")
    Present,
    /// Past ("~~")
    Past,
    /// Future ("^^")
    Future,
}

impl FromStr for TemporalAxis {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "#" => Ok(TemporalAxis::Present),
            "~~" => Ok(TemporalAxis::Past),
            "^^" => Ok(TemporalAxis::Future),
            _ => Err(format!("Invalid temporal axis: {}", s)),
        }
    }
}

impl fmt::Display for TemporalAxis {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            TemporalAxis::Present => "#",
            TemporalAxis::Past => "~~",
            TemporalAxis::Future => "^^",
        };
        write!(f, "{}", s)
    }
}

/// Represents a parsed UCXL address.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct UCXLAddress {
    pub agent: String,
    pub role: Option<String>,
    pub project: String,
    pub task: String,
    pub temporal: TemporalAxis,
    pub path: String,
}

impl FromStr for UCXLAddress {
    type Err = String;
    fn from_str(address: &str) -> Result<Self, Self::Err> {
        // Ensure the scheme is correct
        let scheme_split: Vec<&str> = address.splitn(2, "://").collect();
        if scheme_split.len() != 2 || scheme_split[0] != "ucxl" {
            return Err("Address must start with 'ucxl://'".into());
        }
        let remainder = scheme_split[1];
        // Split at the first '@' to separate agent/role from project/task
        let parts: Vec<&str> = remainder.splitn(2, '@').collect();
        if parts.len() != 2 {
            return Err("Missing '@' separating agent and project".into());
        }
        // Agent and optional role
        let agent_part = parts[0];
        let mut agent_iter = agent_part.splitn(2, ':');
        let agent = agent_iter.next().unwrap().to_string();
        let role = agent_iter.next().map(|s| s.to_string());
        // Project and task
        let project_task_part = parts[1];
        // Find the first '/' that starts the temporal segment and path
        let slash_idx = project_task_part
            .find('/')
            .ok_or("Missing '/' before temporal segment and path")?;
        let (proj_task, after_slash) = project_task_part.split_at(slash_idx);
        let mut proj_task_iter = proj_task.splitn(2, ':');
        let project = proj_task_iter.next().ok_or("Missing project")?.to_string();
        let task = proj_task_iter.next().ok_or("Missing task")?.to_string();
        // after_slash starts with '/', remove it
        let after = &after_slash[1..];
        // Temporal segment is up to the next '/' if present
        let temporal_end = after
            .find('/')
            .ok_or("Missing '/' after temporal segment")?;
        let temporal_str = &after[..temporal_end];
        let temporal = TemporalAxis::from_str(temporal_str)?;
        // The rest is the resource path
        let path = after[temporal_end + 1..].to_string();
        Ok(UCXLAddress {
            agent,
            role,
            project,
            task,
            temporal,
            path,
        })
    }
}

impl fmt::Display for UCXLAddress {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let role_part = if let Some(r) = &self.role {
            format!(":{}", r)
        } else {
            "".to_string()
        };
        write!(
            f,
            "ucxl://{}{}@{}:{}/{}{}",
            self.agent,
            role_part,
            self.project,
            self.task,
            self.temporal,
            if self.path.is_empty() {
                "".to_string()
            } else {
                format!("/{}", self.path)
            }
        )
    }
}

/// Simple in‑memory metadata store mapping a file path to a metadata string.
pub trait MetadataStore {
    fn get(&self, path: &str) -> Option<&String>;
    fn set(&mut self, path: &str, metadata: String);
    fn remove(&mut self, path: &str) -> Option<String> {
        None
    }
}

/// A concrete in‑memory implementation using a HashMap.
pub struct InMemoryMetadataStore {
    map: HashMap<String, String>,
}

impl InMemoryMetadataStore {
    pub fn new() -> Self {
        InMemoryMetadataStore {
            map: HashMap::new(),
        }
    }
}

impl MetadataStore for InMemoryMetadataStore {
    fn get(&self, path: &str) -> Option<&String> {
        self.map.get(path)
    }
    fn set(&mut self, path: &str, metadata: String) {
        self.map.insert(path.to_string(), metadata);
    }
    fn remove(&mut self, path: &str) -> Option<String> {
        self.map.remove(path)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_temporal_from_str() {
        assert_eq!(TemporalAxis::from_str("#").unwrap(), TemporalAxis::Present);
        assert_eq!(TemporalAxis::from_str("~~").unwrap(), TemporalAxis::Past);
        assert_eq!(TemporalAxis::from_str("^^").unwrap(), TemporalAxis::Future);
    }

    #[test]
    fn test_ucxl_address_parsing() {
        let addr_str = "ucxl://alice:admin@myproj:task1/#/docs/readme.md";
        let addr = UCXLAddress::from_str(addr_str).unwrap();
        assert_eq!(addr.agent, "alice");
        assert_eq!(addr.role, Some("admin".to_string()));
        assert_eq!(addr.project, "myproj");
        assert_eq!(addr.task, "task1");
        assert_eq!(addr.temporal, TemporalAxis::Present);
        assert_eq!(addr.path, "docs/readme.md");
        assert_eq!(addr.to_string(), addr_str);
    }

    #[test]
    fn test_metadata_store() {
        let mut store = InMemoryMetadataStore::new();
        store.set("/foo.txt", "meta".into());
        assert_eq!(store.get("/foo.txt"), Some(&"meta".to_string()));
        store.remove("/foo.txt");
        assert!(store.get("/foo.txt").is_none());
    }
}
