//! Wall-clock helpers — civil calendar without a `chrono` dependency.
//!
//! `omne-cli` needs two stamp formats: `YYYY-MM-DD` for the volume
//! README (`omne.md` frontmatter) and `YYYY-MM-DDTHH:MM:SSZ` for every
//! entry in `events.jsonl`. Both are cheap if you have Howard Hinnant's
//! public-domain civil-calendar algorithm, and we already do — but
//! Unit 11 duplicated the 15-line arithmetic into `executor.rs` without
//! sharing with `commands::init::chrono_today`. This module is the
//! single implementation; the two callers compose the format string
//! they need from the returned parts.

#![allow(dead_code)]

use std::time::SystemTime;

/// Broken-down UTC date/time from a Unix timestamp.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct CivilUtc {
    pub year: i64,
    pub month: u64,
    pub day: u64,
    pub hour: u64,
    pub minute: u64,
    pub second: u64,
}

impl CivilUtc {
    /// `YYYY-MM-DD`.
    pub fn format_date(&self) -> String {
        format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
    }

    /// `YYYY-MM-DDTHH:MM:SSZ`.
    pub fn format_iso_utc(&self) -> String {
        format!(
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
            self.year, self.month, self.day, self.hour, self.minute, self.second
        )
    }
}

/// Current UTC instant as a [`CivilUtc`]. Falls back to the Unix epoch
/// if the system clock is before `1970-01-01` (no `Result` — a VM with
/// a broken clock should see `1970-01-01T00:00:00Z`, which is loud
/// enough to be noticed).
pub fn now_utc() -> CivilUtc {
    from_system_time(SystemTime::now())
}

pub fn from_system_time(t: SystemTime) -> CivilUtc {
    let duration = t.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
    from_unix_seconds(duration.as_secs())
}

/// Decompose a Unix second count into civil UTC parts. Public for
/// deterministic unit tests that pin a specific instant.
pub fn from_unix_seconds(secs: u64) -> CivilUtc {
    let days = secs / 86_400;
    let sod = secs % 86_400;

    // Howard Hinnant civil calendar (public domain).
    let z = days as i64 + 719_468;
    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
    let doe = (z - era * 146_097) as u64;
    let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
    let y = (yoe as i64) + era * 400;
    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
    let mp = (5 * doy + 2) / 153;
    let d = doy - (153 * mp + 2) / 5 + 1;
    let m = if mp < 10 { mp + 3 } else { mp - 9 };
    let y = if m <= 2 { y + 1 } else { y };

    CivilUtc {
        year: y,
        month: m,
        day: d,
        hour: sod / 3_600,
        minute: (sod % 3_600) / 60,
        second: sod % 60,
    }
}

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

    #[test]
    fn unix_epoch_is_1970_01_01() {
        let c = from_unix_seconds(0);
        assert_eq!(c.format_date(), "1970-01-01");
        assert_eq!(c.format_iso_utc(), "1970-01-01T00:00:00Z");
    }

    #[test]
    fn pinned_midday_instant_formats_correctly() {
        // 2024-03-15T12:34:56Z = 1_710_506_096 seconds since epoch.
        // Derivation: 2024-01-01T00:00Z = 1_704_067_200s;
        //   +31 (Jan) +29 (Feb leap) +14 (Mar pre-15) = 74 days = 6_393_600s;
        //   +12h34m56s = 45_296s; total = 1_710_506_096s.
        let c = from_unix_seconds(1_710_506_096);
        assert_eq!(c.year, 2024);
        assert_eq!(c.month, 3);
        assert_eq!(c.day, 15);
        assert_eq!(c.hour, 12);
        assert_eq!(c.minute, 34);
        assert_eq!(c.second, 56);
        assert_eq!(c.format_iso_utc(), "2024-03-15T12:34:56Z");
    }

    #[test]
    fn now_utc_is_plausible_shape() {
        let s = now_utc().format_iso_utc();
        assert_eq!(s.len(), 20);
        assert!(s.ends_with('Z'));
        assert!(s.contains('T'));
    }
}
