// runtime/time.el — Time operations, sleep, and formatting.
//
// Implements the time surface from runtime/el_runtime.c
// (lines 3334–3440, 3471–3656) in pure El, using seed primitives.
//
// Seed primitives consumed:
//   __time_now_ns()                     -> Int   (nanoseconds since Unix epoch)
//   __sleep_ms(n: Int)
//   __int_to_str(n: Int)               -> String
//   __str_to_int(s: String)            -> Int
//   __float_to_str(f: Float)           -> String

// ---------------------------------------------------------------------------
// Core — now / sleep
// ---------------------------------------------------------------------------

// time_now — milliseconds since Unix epoch (UTC). Matches legacy time_now().
fn time_now() -> Int {
    return __time_now_ns() / 1000000
}

// time_now_utc — same as time_now; UTC alias kept for compatibility.
fn time_now_utc() -> Int {
    return __time_now_ns() / 1000000
}

// now_ns — nanoseconds since Unix epoch. Matches el_now_instant().
fn now_ns() -> Int {
    return __time_now_ns()
}

// unix_timestamp — whole seconds since Unix epoch. Matches unix_timestamp().
fn unix_timestamp() -> Int {
    return __time_now_ns() / 1000000000
}

// sleep_secs — block for n seconds. Clamps negatives to 0.
fn sleep_secs(n: Int) {
    if n < 0 {
        __sleep_ms(0)
    } else {
        __sleep_ms(n * 1000)
    }
}

// sleep_ms — block for n milliseconds. Clamps negatives to 0.
fn sleep_ms(n: Int) {
    if n < 0 {
        __sleep_ms(0)
    } else {
        __sleep_ms(n)
    }
}

// ---------------------------------------------------------------------------
// Gregorian decomposition helpers — pure integer arithmetic.
//
// Algorithm: civil date from days since Unix epoch (1970-01-01).
// Based on Howard Hinnant's public-domain civil_from_days formula
// (http://howardhinnant.github.io/date_algorithms.html), which the legacy
// gmtime_r call performs under the hood.
//
// We expose the pieces as private helpers (leading underscore convention).
// ---------------------------------------------------------------------------

// _is_leap — 1 if year y is a Gregorian leap year, 0 otherwise.
fn _is_leap(y: Int) -> Int {
    if y % 400 == 0 { return 1 }
    if y % 100 == 0 { return 0 }
    if y % 4 == 0 { return 1 }
    return 0
}

// _days_in_month — number of days in month m of year y (m: 1..12).
fn _days_in_month(y: Int, m: Int) -> Int {
    if m == 1 { return 31 }
    if m == 2 {
        if _is_leap(y) == 1 { return 29 }
        return 28
    }
    if m == 3 { return 31 }
    if m == 4 { return 30 }
    if m == 5 { return 31 }
    if m == 6 { return 30 }
    if m == 7 { return 31 }
    if m == 8 { return 31 }
    if m == 9 { return 30 }
    if m == 10 { return 31 }
    if m == 11 { return 30 }
    return 31
}

// _pad2 — zero-pad an integer to at least 2 digits.
fn _pad2(n: Int) -> String {
    if n < 10 { return "0" + __int_to_str(n) }
    return __int_to_str(n)
}

// _pad4 — zero-pad an integer to at least 4 digits.
fn _pad4(n: Int) -> String {
    if n < 10   { return "000" + __int_to_str(n) }
    if n < 100  { return "00" + __int_to_str(n) }
    if n < 1000 { return "0" + __int_to_str(n) }
    return __int_to_str(n)
}

// _pad3 — zero-pad an integer to at least 3 digits (milliseconds).
fn _pad3(n: Int) -> String {
    if n < 10  { return "00" + __int_to_str(n) }
    if n < 100 { return "0" + __int_to_str(n) }
    return __int_to_str(n)
}

// _civil_year_month_day — decompose days-since-epoch (z, may be negative)
// into year/month/day using the civil_from_days algorithm.
// Returns a JSON object: {"year":Y,"month":M,"day":D}
fn _civil_ymd(z: Int) -> String {
    // shift epoch to 0000-03-01 (makes leap-day math clean)
    let zz: Int = z + 719468
    // era: 400-year block
    let era: Int = zz / 146097
    if zz < 0 {
        era = (zz - 146096) / 146097
    }
    let doe: Int = zz - era * 146097                     // day-of-era [0, 146096]
    let yoe: Int = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365  // year-of-era [0, 399]
    let y: Int = yoe + era * 400
    let doy: Int = doe - (365 * yoe + yoe / 4 - yoe / 100) // day-of-year [0, 365]
    let mp: Int = (5 * doy + 2) / 153                   // month in [0, 11] from March
    let d: Int = doy - (153 * mp + 2) / 5 + 1           // day [1, 31]
    let m: Int = mp + 3
    if mp >= 10 { m = mp - 9 }
    if mp >= 10 { y = y + 1 }
    return "{\"year\":" + __int_to_str(y) + ",\"month\":" + __int_to_str(m) + ",\"day\":" + __int_to_str(d) + "}"
}

// ---------------------------------------------------------------------------
// time_to_parts — decompose a millisecond timestamp into UTC components.
//
// Returns a JSON string:
//   {"year":Y,"month":M,"day":D,"hour":H,"minute":M,"second":S,"ms":MS}
//
// Matches legacy time_to_parts() which returns an ElMap with the same keys.
// ---------------------------------------------------------------------------

fn time_to_parts(ts: Int) -> String {
    let ms_raw: Int = ts % 1000
    let ms: Int = ms_raw
    if ms_raw < 0 { ms = ms_raw + 1000 }

    let s_raw: Int = ts / 1000
    let s: Int = s_raw
    if ms_raw < 0 { s = s_raw - 1 }

    // seconds within the day and days since epoch
    let sec_of_day: Int = s % 86400
    let day_z: Int = s / 86400
    // handle negative: floor division
    if sec_of_day < 0 {
        sec_of_day = sec_of_day + 86400
        day_z = day_z - 1
    }

    let hour: Int = sec_of_day / 3600
    let rem: Int = sec_of_day % 3600
    let minute: Int = rem / 60
    let second: Int = rem % 60

    // date components via civil decomposition
    let ymd: String = _civil_ymd(day_z)
    let year: Int = __str_to_int(json_get(ymd, "year"))
    let month: Int = __str_to_int(json_get(ymd, "month"))
    let day: Int = __str_to_int(json_get(ymd, "day"))

    return "{\"year\":" + __int_to_str(year) +
           ",\"month\":" + __int_to_str(month) +
           ",\"day\":" + __int_to_str(day) +
           ",\"hour\":" + __int_to_str(hour) +
           ",\"minute\":" + __int_to_str(minute) +
           ",\"second\":" + __int_to_str(second) +
           ",\"ms\":" + __int_to_str(ms) + "}"
}

// ---------------------------------------------------------------------------
// time_format — format a millisecond timestamp as a string.
//
// fmt "ISO" (or empty) → "YYYY-MM-DDTHH:MM:SS.mmmZ"  (ISO 8601 UTC)
// Other fmt values are passed as a strftime-style hint; the El runtime
// implements the most common tokens. Unsupported tokens are passed through.
//
// Matches legacy time_format().
// ---------------------------------------------------------------------------

fn time_format(ts: Int, fmt: String) -> String {
    let parts: String = time_to_parts(ts)
    let y: Int = __str_to_int(json_get(parts, "year"))
    let mo: Int = __str_to_int(json_get(parts, "month"))
    let d: Int = __str_to_int(json_get(parts, "day"))
    let h: Int = __str_to_int(json_get(parts, "hour"))
    let mi: Int = __str_to_int(json_get(parts, "minute"))
    let s: Int = __str_to_int(json_get(parts, "second"))
    let ms: Int = __str_to_int(json_get(parts, "ms"))

    // ISO 8601 UTC: YYYY-MM-DDTHH:MM:SS.mmmZ
    if str_eq(fmt, "ISO") {
        return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
               "T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
               "." + _pad3(ms) + "Z"
    }
    if str_eq(fmt, "") {
        return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
               "T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
               "." + _pad3(ms) + "Z"
    }

    // strftime-subset: replace common tokens
    let out: String = fmt
    let out = str_replace(out, "%Y", _pad4(y))
    let out = str_replace(out, "%m", _pad2(mo))
    let out = str_replace(out, "%d", _pad2(d))
    let out = str_replace(out, "%H", _pad2(h))
    let out = str_replace(out, "%M", _pad2(mi))
    let out = str_replace(out, "%S", _pad2(s))
    let out = str_replace(out, "%3N", _pad3(ms))
    return out
}

// ---------------------------------------------------------------------------
// time_from_parts — construct a ms timestamp from seconds + nanosecond offset.
//
// Matches legacy time_from_parts(secs, ns, tz) — tz is ignored (UTC assumed).
// ---------------------------------------------------------------------------

fn time_from_parts(secs: Int, ns: Int, tz: String) -> Int {
    return secs * 1000 + ns / 1000000
}

// ---------------------------------------------------------------------------
// time_add — add a duration to a millisecond timestamp.
//
// unit: "ms" | "sec" | "min" | "hour" | "day"
// Matches legacy time_add().
// ---------------------------------------------------------------------------

fn time_add(ts: Int, n: Int, unit: String) -> Int {
    if str_eq(unit, "ms")   { return ts + n }
    if str_eq(unit, "sec")  { return ts + n * 1000 }
    if str_eq(unit, "min")  { return ts + n * 60000 }
    if str_eq(unit, "hour") { return ts + n * 3600000 }
    if str_eq(unit, "day")  { return ts + n * 86400000 }
    // default: treat as ms
    return ts + n
}

// ---------------------------------------------------------------------------
// time_diff — compute the difference between two millisecond timestamps.
//
// Returns ts2 - ts1 in the given unit.
// unit: "ms" | "sec" | "min" | "hour" | "day"
// Matches legacy time_diff().
// ---------------------------------------------------------------------------

fn time_diff(ts1: Int, ts2: Int, unit: String) -> Int {
    let d: Int = ts2 - ts1
    if str_eq(unit, "ms")   { return d }
    if str_eq(unit, "sec")  { return d / 1000 }
    if str_eq(unit, "min")  { return d / 60000 }
    if str_eq(unit, "hour") { return d / 3600000 }
    if str_eq(unit, "day")  { return d / 86400000 }
    return d
}

// ---------------------------------------------------------------------------
// Instant / Duration — nanosecond-precision temporal types.
//
// These match the el_now_instant, duration_seconds, duration_millis, etc.
// family from legacy lines 3471–3656. Both Instant and Duration are Int
// (nanoseconds); the type distinction is at the call-site convention level.
// ---------------------------------------------------------------------------

// now — current Instant in nanoseconds. Alias for __time_now_ns().
fn now() -> Int {
    return __time_now_ns()
}

// unix_seconds — Instant from whole seconds since epoch.
fn unix_seconds(n: Int) -> Int {
    return n * 1000000000
}

// unix_millis — Instant from milliseconds since epoch.
fn unix_millis(n: Int) -> Int {
    return n * 1000000
}

// instant_to_unix_seconds — convert Instant nanoseconds to whole seconds.
fn instant_to_unix_seconds(i: Int) -> Int {
    return i / 1000000000
}

// instant_to_unix_millis — convert Instant nanoseconds to milliseconds.
fn instant_to_unix_millis(i: Int) -> Int {
    return i / 1000000
}

// instant_to_iso8601 — format an Instant (nanoseconds) as ISO 8601 UTC.
fn instant_to_iso8601(i: Int) -> String {
    let ms: Int = i / 1000000
    return time_format(ms, "ISO")
}

// duration_seconds — Duration from n whole seconds.
fn duration_seconds(n: Int) -> Int {
    return n * 1000000000
}

// duration_millis — Duration from n milliseconds.
fn duration_millis(n: Int) -> Int {
    return n * 1000000
}

// duration_nanos — Duration from n nanoseconds (identity).
fn duration_nanos(n: Int) -> Int {
    return n
}

// duration_to_seconds — convert a Duration (nanoseconds) to whole seconds.
fn duration_to_seconds(d: Int) -> Int {
    return d / 1000000000
}

// duration_to_millis — convert a Duration (nanoseconds) to milliseconds.
fn duration_to_millis(d: Int) -> Int {
    return d / 1000000
}

// duration_to_nanos — return the Duration as nanoseconds (identity).
fn duration_to_nanos(d: Int) -> Int {
    return d
}

// sleep_duration — sleep for a Duration (nanoseconds). Clamps negatives to 0.
fn sleep_duration(dur: Int) {
    let ms: Int = dur / 1000000
    if ms < 0 {
        __sleep_ms(0)
    } else {
        __sleep_ms(ms)
    }
}

// ---------------------------------------------------------------------------
// TTL cache — time-bounded key/value backed by state.
//
// Matches legacy ttl_cache_set / ttl_cache_get / ttl_cache_age (lines 3663–3717).
// max_age is a Duration (nanoseconds).
// ---------------------------------------------------------------------------

// ttl_cache_set — store a value and record the current Instant for TTL checks.
fn ttl_cache_set(key: String, value: String) {
    state_set(key, value)
    let stamp_key: String = "__ttl_at:" + key
    let now_str: String = __int_to_str(__time_now_ns())
    state_set(stamp_key, now_str)
}

// ttl_cache_get — return value if age < max_age (nanoseconds), else "".
fn ttl_cache_get(key: String, max_age: Int) -> String {
    let stamp_key: String = "__ttl_at:" + key
    let sv: String = state_get(stamp_key)
    if str_eq(sv, "") { return "" }
    let set_at: Int = __str_to_int(sv)
    let now_ns: Int = __time_now_ns()
    let age: Int = now_ns - set_at
    if age < 0 { return "" }
    if age > max_age { return "" }
    return state_get(key)
}

// ttl_cache_age — nanoseconds since a key was last set (INT_MAX sentinel if missing).
fn ttl_cache_age(key: String) -> Int {
    let stamp_key: String = "__ttl_at:" + key
    let sv: String = state_get(stamp_key)
    if str_eq(sv, "") { return 9223372036854775807 }
    let set_at: Int = __str_to_int(sv)
    return __time_now_ns() - set_at
}

// ---------------------------------------------------------------------------
// uuid_new / uuid_v4 — generate a UUID v4 string.
//
// Delegates to the __uuid_v4() seed primitive.
// Matches legacy uuid_new() / uuid_v4() (lines 4602–4621).
// ---------------------------------------------------------------------------

fn uuid_new() -> String {
    return __uuid_v4()
}

fn uuid_v4() -> String {
    return __uuid_v4()
}

// ---------------------------------------------------------------------------
// DHARMA-compatible aliases — millisecond-precision timestamps.
//
// now_millis, unix_timestamp_ms, and time_now_ms all return the same value:
// milliseconds since the Unix epoch. They exist because different parts of
// the dharma codebase use different names for the same concept.
// ---------------------------------------------------------------------------

// now_millis — milliseconds since Unix epoch. Alias for time_now().
fn now_millis() -> Int {
    return __time_now_ns() / 1000000
}

// unix_timestamp_ms — same as now_millis.
fn unix_timestamp_ms() -> Int {
    return __time_now_ns() / 1000000
}

// time_now_ms — same as now_millis.
fn time_now_ms() -> Int {
    return __time_now_ns() / 1000000
}
