# ━━━ util.ling — miao helpers: lists, string compare, sort, paths, time ━━━
# Part of ling-miao (庙), the Ling version control system.
# All fns prefixed ut_ (flat namespace).

fn ut_sel(c, a, b) {
    if c { return a }
    return b
}

fn ut_is_bad(r) {
    match r { bad(e) => { return true } ok(v) => { return false } }
}

fn ut_val(r) { return r.unwrap() }

# ── list helpers ──────────────────────────────────────────────────────
fn ut_contains(l, v) {
    bind i = 0.0
    while i < len(l) {
        if list_get(l, i) == v { return true }
        bind i = i + 1.0
    }
    return false
}

fn ut_index_of(l, v) {
    bind i = 0.0
    while i < len(l) {
        if list_get(l, i) == v { return i }
        bind i = i + 1.0
    }
    return 0.0 - 1.0
}

# slice of a list [from, to)
fn ut_lslice(l, from, to) {
    bind out = list_new()
    bind i = from
    while i < to {
        bind out = list_push(out, list_get(l, i))
        bind i = i + 1.0
    }
    return out
}

# ── lexicographic string compare (UTF-8 byte order) ───────────────────
# strings only support == in Ling; compare via hex bytes.
fn ut_str_lt(a, b) {
    bind ha = text_hex(a)
    bind hb = text_hex(b)
    bind la = len(ha)
    bind lb = len(hb)
    bind n = min(la, lb)
    bind i = 0.0
    while i < n {
        bind ca = hex_to_number(str_slice(ha, i, 2.0))
        bind cb = hex_to_number(str_slice(hb, i, 2.0))
        if ca < cb { return true }
        if ca > cb { return false }
        bind i = i + 2.0
    }
    return la < lb
}

# iterative bottom-up merge sort of a list of strings
fn ut_sort(l) {
    bind n = len(l)
    if n < 2.0 { return l }
    bind cur = l
    bind width = 1.0
    while width < n {
        bind nxt = list_new()
        bind pos = 0.0
        while pos < n {
            bind mid = min(pos + width, n)
            bind hi = min(pos + width * 2.0, n)
            # merge cur[pos..mid] with cur[mid..hi]
            bind i = pos
            bind j = mid
            while i < mid || j < hi {
                if i >= mid {
                    bind nxt = list_push(nxt, list_get(cur, j))
                    bind j = j + 1.0
                } else if j >= hi {
                    bind nxt = list_push(nxt, list_get(cur, i))
                    bind i = i + 1.0
                } else if ut_str_lt(list_get(cur, j), list_get(cur, i)) {
                    bind nxt = list_push(nxt, list_get(cur, j))
                    bind j = j + 1.0
                } else {
                    bind nxt = list_push(nxt, list_get(cur, i))
                    bind i = i + 1.0
                }
            }
            bind pos = hi
        }
        bind cur = nxt
        bind width = width * 2.0
    }
    return cur
}

# sort keys and keep a parallel vals list aligned (bottom-up merge sort).
# returns [sorted_keys, sorted_vals]
fn ut_sort_pair(keys, vals) {
    bind n = len(keys)
    if n < 2.0 { return [keys, vals] }
    bind ck = keys
    bind cv = vals
    bind width = 1.0
    while width < n {
        bind nk = list_new()
        bind nv = list_new()
        bind pos = 0.0
        while pos < n {
            bind mid = min(pos + width, n)
            bind hi = min(pos + width * 2.0, n)
            bind i = pos
            bind j = mid
            while i < mid || j < hi {
                if i >= mid {
                    bind nk = list_push(nk, list_get(ck, j))
                    bind nv = list_push(nv, list_get(cv, j))
                    bind j = j + 1.0
                } else if j >= hi {
                    bind nk = list_push(nk, list_get(ck, i))
                    bind nv = list_push(nv, list_get(cv, i))
                    bind i = i + 1.0
                } else if ut_str_lt(list_get(ck, j), list_get(ck, i)) {
                    bind nk = list_push(nk, list_get(ck, j))
                    bind nv = list_push(nv, list_get(cv, j))
                    bind j = j + 1.0
                } else {
                    bind nk = list_push(nk, list_get(ck, i))
                    bind nv = list_push(nv, list_get(cv, i))
                    bind i = i + 1.0
                }
            }
            bind pos = hi
        }
        bind ck = nk
        bind cv = nv
        bind width = width * 2.0
    }
    return [ck, cv]
}

# ── paths ─────────────────────────────────────────────────────────────
fn ut_norm(p) {
    bind q = str_replace(p, "\\", "/")
    # strip trailing "/." and trailing "/"
    if str_ends_with(q, "/.") { bind q = str_slice(q, 0.0, len(q) - 2.0) }
    if len(q) > 1.0 {
        if str_ends_with(q, "/") { bind q = str_slice(q, 0.0, len(q) - 1.0) }
    }
    return q
}

fn ut_last_index(s, needle) {
    bind best = 0.0 - 1.0
    bind off = 0.0
    bind go = true
    while go {
        bind rest = str_slice(s, off, len(s) - off)
        bind f = str_find(rest, needle)
        if f < 0.0 {
            bind go = false
        } else {
            bind best = off + f
            bind off = off + f + 1.0
        }
    }
    return best
}

fn ut_parent(p) {
    bind q = ut_norm(p)
    bind i = ut_last_index(q, "/")
    if i < 0.0 { return q }
    if i < 1.0 { return "/" }
    bind r = str_slice(q, 0.0, i)
    # "C:" root guard
    if str_ends_with(r, ":") { return r + "/" }
    return r
}

fn ut_basename(p) { return path_name(p) }

# ── time: unix seconds -> "YYYY-MM-DD HH:MM" (UTC) ────────────────────
fn ut_pad2(n) {
    if n < 10.0 { return "0" + n }
    return "" + n
}

fn ut_fmt_time(unix) {
    bind t = floor(unix)
    bind days = floor(t / 86400.0)
    bind secs = t - days * 86400.0
    bind z = days + 719468.0
    bind era = floor(z / 146097.0)
    bind doe = z - era * 146097.0
    bind yoe = floor((doe - floor(doe / 1460.0) + floor(doe / 36524.0) - floor(doe / 146096.0)) / 365.0)
    bind y = yoe + era * 400.0
    bind doy = doe - (365.0 * yoe + floor(yoe / 4.0) - floor(yoe / 100.0))
    bind mp = floor((5.0 * doy + 2.0) / 153.0)
    bind d = doy - floor((153.0 * mp + 2.0) / 5.0) + 1.0
    bind m = mp + 3.0
    if mp >= 10.0 { bind m = mp - 9.0 }
    if m <= 2.0 { bind y = y + 1.0 }
    bind hh = floor(secs / 3600.0)
    bind mm = floor((secs - hh * 3600.0) / 60.0)
    return y + "-" + ut_pad2(m) + "-" + ut_pad2(d) + " " + ut_pad2(hh) + ":" + ut_pad2(mm)
}

# ── padding ───────────────────────────────────────────────────────────
fn ut_pad(s, n) {
    bind out = s
    while len(out) < n {
        bind out = out + " "
    }
    return out
}

# ── text files ────────────────────────────────────────────────────────
fn ut_read_text(path) {
    if file_exists(path) { return read_file(path) }
    return ""
}

# non-empty lines of a file
fn ut_read_lines(path) {
    bind t = ut_read_text(path)
    bind out = list_new()
    if t == "" { return out }
    bind parts = split(t, "\n")
    bind i = 0.0
    while i < len(parts) {
        bind ln = list_get(parts, i)
        if ln != "" { bind out = list_push(out, ln) }
        bind i = i + 1.0
    }
    return out
}

fn ut_short(id) {
    if len(id) < 7.0 { return id }
    return str_slice(id, 0.0, 7.0)
}

# first line of a text
fn ut_first_line(t) {
    bind f = str_find(t, "\n")
    if f < 0.0 { return t }
    return str_slice(t, 0.0, f)
}
