// epm/src/install.el — dependency resolution and vessel installation
//
// Reads the current project's manifest.el, walks the full dependency graph
// by fetching each dep from Engram, detects cycles, and produces a
// topological install order (dependencies before dependents).
//
// Installed vessels land in: .epm/vessels/<name>/
//
// Cycle detection: each recursion carries a chain of names currently on
// the call stack. If a dep already appears in the chain it's a cycle.
//
// Algorithm:
//   1. Parse manifest.el to get direct deps
//   2. For each dep, fetch its manifest from Engram
//   3. Recurse into that dep's own deps (depth-first)
//   4. Append dep to order after all its transitive deps
//   5. Deduplicate: skip already-ordered vessels

// ── List helpers (operating on String lists) ──────────────────────────────────

// list_contains returns true if item appears in lst.
fn list_contains(lst: [String], item: String) -> Bool {
    let n: Int = native_list_len(lst)
    let i: Int = 0
    while i < n {
        let v: String = native_list_get(lst, i)
        if str_eq(v, item) { return true }
        let i = i + 1
    }
    return false
}

// ── Dependency graph walker ───────────────────────────────────────────────────

// resolve_deps_recursive fetches a vessel's metadata from Engram and walks
// its dependency list depth-first, building a topological order.
//
// Parameters:
//   name     — vessel name to resolve
//   version  — version string (may be "" for any)
//   chain    — names currently on the recursion stack (cycle detection)
//   visited  — names already appended to order (dedup)
//   order    — accumulated result (list of "name:version" strings)
//
// Returns a Map with keys "visited", "order", and "ok" (Bool as String).
fn resolve_deps_recursive(name: String, version: String, chain: [String], visited: [String], order: [String]) -> Map<String, Any> {
    let key: String = name + ":" + version

    // Skip if already ordered
    if list_contains(visited, key) {
        return { "visited": visited, "order": order, "ok": "true" }
    }

    // Cycle detection: if name is already in the call chain, it's a cycle
    if list_contains(chain, name) {
        println("epm: error: dependency cycle detected involving '" + name + "'")
        return { "visited": visited, "order": order, "ok": "false" }
    }

    // Push this name onto the cycle-detection chain
    let chain = native_list_append(chain, name)

    // Fetch vessel metadata from Engram
    let content: String = registry_find(name, version)
    if str_eq(content, "") {
        println("epm: error: vessel '" + name + "' version '" + version + "' not found in registry")
        return { "visited": visited, "order": order, "ok": "false" }
    }

    // content is a JSON blob: {"name":..., "version":..., "deps":[...]}
    let actual_version: String = json_get_string(content, "version")
    let actual_key: String = name + ":" + actual_version
    let deps_json: String = json_get_raw(content, "deps")

    // Walk this vessel's dependencies first (depth-first)
    let dep_count: Int = json_array_len(deps_json)
    let di: Int = 0
    while di < dep_count {
        let dep: String    = json_array_get(deps_json, di)
        let dep_name: String  = json_get_string(dep, "name")
        let dep_ver: String   = json_get_string(dep, "version")

        let r = resolve_deps_recursive(dep_name, dep_ver, chain, visited, order)
        let ok_str: String = r["ok"]
        if str_eq(ok_str, "false") {
            return r
        }
        let visited = r["visited"]
        let order   = r["order"]

        let di = di + 1
    }

    // Now append this vessel (after all its deps)
    let visited = native_list_append(visited, actual_key)
    let order   = native_list_append(order, actual_key)

    return { "visited": visited, "order": order, "ok": "true" }
}

// ── Install order ─────────────────────────────────────────────────────────────

// resolve_install_order reads manifest.el from the current directory and
// returns a list of "name:version" strings in topological install order.
//
// Returns an empty list on failure (error is printed to stdout).
fn resolve_install_order() -> [String] {
    let src: String = fs_read("manifest.el")
    if str_eq(src, "") {
        println("epm: no manifest.el found in current directory")
        return native_list_empty()
    }

    let deps_json: String = manifest_deps(src)
    let dep_count: Int = json_array_len(deps_json)
    if dep_count == 0 {
        println("epm: no dependencies declared in manifest.el")
        return native_list_empty()
    }

    let chain: [String]   = native_list_empty()
    let visited: [String] = native_list_empty()
    let order: [String]   = native_list_empty()

    let i: Int = 0
    while i < dep_count {
        let dep: String      = json_array_get(deps_json, i)
        let dep_name: String = json_get_string(dep, "name")
        let dep_ver: String  = json_get_string(dep, "version")

        let r = resolve_deps_recursive(dep_name, dep_ver, chain, visited, order)
        let ok_str: String = r["ok"]
        if str_eq(ok_str, "false") {
            return native_list_empty()
        }
        let visited = r["visited"]
        let order   = r["order"]

        let i = i + 1
    }

    return order
}

// ── Installation ──────────────────────────────────────────────────────────────

// install_vessel fetches a vessel's source from Engram and writes it
// to .epm/vessels/<name>/.
//
// Currently writes the content JSON blob as a manifest record so that
// other tools can introspect what is installed. When Engram stores actual
// source archives, this function would extract them.
//
// Returns true on success.
fn install_vessel(name: String, version: String) -> Bool {
    let content: String = registry_find(name, version)
    if str_eq(content, "") {
        println("epm: error: cannot fetch '" + name + "' '" + version + "' from registry")
        return false
    }

    let install_dir: String = ".epm/vessels/" + name
    let mkdir_ret: Int = exec_command("mkdir -p " + install_dir)

    // Write the content blob as installed.json so downstream tools know what's there
    let dest: String = install_dir + "/installed.json"
    fs_write(dest, content)

    println("epm: installed " + name + " " + version + " -> " + dest)
    return true
}

// ── Top-level install ─────────────────────────────────────────────────────────

// cmd_install resolves and installs all dependencies for the current project.
fn cmd_install() -> Void {
    let src: String = fs_read("manifest.el")
    if str_eq(src, "") {
        println("epm: no manifest.el found in current directory")
        exit(1)
    }

    let pkg_name: String = manifest_name(src)
    println("epm: resolving dependencies for " + pkg_name)

    let order: [String] = resolve_install_order()
    let n: Int = native_list_len(order)

    if n == 0 {
        println("epm: nothing to install")
        return
    }

    println("epm: install order (" + native_int_to_str(n) + " vessels):")
    let i: Int = 0
    while i < n {
        let entry: String = native_list_get(order, i)
        println("  " + entry)
        let i = i + 1
    }

    // Ensure .epm directory exists
    let mk2: Int = exec_command("mkdir -p .epm/vessels")

    // Install each vessel in topological order
    let j: Int = 0
    while j < n {
        let kv: String = native_list_get(order, j)
        // Split "name:version" on the last colon
        let colon: Int = str_index_of(kv, ":")
        if colon < 0 {
            println("epm: error: malformed order entry: " + kv)
            exit(1)
        }
        let vname: String   = str_slice(kv, 0, colon)
        let vver: String    = str_slice(kv, colon + 1, str_len(kv))
        let ok: Bool = install_vessel(vname, vver)
        if !ok {
            println("epm: install failed at " + kv)
            exit(1)
        }
        let j = j + 1
    }

    println("epm: all dependencies installed")
}
