//! Globally unique workspace ID.
//!
//! This module provides functionality for generating and managing a globally
//! unique workspace ID.
//!
//! The ID is stored in [`crate::Workspace::id`] and can be persisted to disk
//! using [`Id::store`].

use std::{
    fmt, fs, io,
    ops::Deref,
    path::Path,
    str::FromStr,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use camino::Utf8Path;

use crate::{Error, Result};

static ID_CHARS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
static ID_LEN: usize = 5;
static ID_FILE: &str = ".id";
static ID_PREAMBLE: &str = "DO NOT EDIT THIS FILE! IT IS AUTO-GENERATED BY JP.";

/// A globally unique workspace ID.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(String);

impl Id {
    /// Generate a globally unique workspace ID.
    ///
    /// Generating a new ID **DOES NOT** persist it to disk.
    /// Use [`Id::store`] to persist the ID.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Load the globally unique workspace ID from the given root path.
    pub fn load(root: impl AsRef<Path>) -> Option<Result<Self>> {
        fs::read_to_string(root.as_ref().join(ID_FILE))
            .ok()?
            .lines()
            .last()
            .map(FromStr::from_str)
    }

    /// Store the globally unique workspace ID to the given root path.
    pub fn store(&self, storage: &Utf8Path) -> io::Result<()> {
        fs::write(
            storage.join(ID_FILE),
            format!("{ID_PREAMBLE}\n{}\n", self.0),
        )
    }
}

impl Default for Id {
    fn default() -> Self {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or(Duration::ZERO)
            .as_millis();

        generate_id_from_timestamp(timestamp)
    }
}

impl Deref for Id {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Display for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl FromStr for Id {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        if s.len() != ID_LEN {
            return Err(Error::Id(format!(
                "Workspace ID must be {ID_LEN} characters long"
            )));
        }

        if !s.chars().all(|c| ID_CHARS.contains(&(c as u8))) {
            return Err(Error::Id(format!(
                "Workspace ID must only contain characters {ID_CHARS:?}"
            )));
        }

        Ok(Self(s.to_owned()))
    }
}

/// Generates a fixed-length ID from a timestamp using Base63 encoding.
///
/// Takes the least significant digits of the Base63 representation of the
/// timestamp.
/// Pads with '0' if the representation is too short.
fn generate_id_from_timestamp(ts: u128) -> Id {
    if ts == 0 {
        return Id("0".repeat(ID_LEN));
    }

    let base = ID_CHARS.len() as u128;
    let mut num = ts;
    let mut result = vec![b'0'; ID_LEN]; // Pre-fill with '0's

    // Start filling from the rightmost position
    let mut pos = ID_LEN;

    // Generate base36 digits and place from right to left
    while num > 0 && pos > 0 {
        pos -= 1;
        let remainder = (num % base) as usize;
        result[pos] = ID_CHARS[remainder];
        num /= base;
    }

    Id(String::from_utf8(result).expect("valid UTF-8"))
}
