// epm/src/registry.el — Gitea-backed vessel registry
//
// Vessels are stored as Gitea repositories under a shared org:
//
//   repo:     <org>/<name>   at https://git.neuralplatform.ai
//   versions: Gitea release tags (e.g. "0.1.0")
//   archive:  release asset named "<name>-<version>.tar.gz"
//
// All registry read operations use unauthenticated HTTP GET.
// registry_publish requires EPM_TOKEN to be set.
//
// Configuration (environment variables):
//   EPM_GITEA_API  — Gitea API base URL (default: https://git.neuralplatform.ai/api/v1)
//   EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
//   EPM_TOKEN      — Gitea personal access token (required for publish)

// ── Cross-module forward declarations ─────────────────────────────────────────
// These symbols are defined in sibling epm modules or the El runtime and are
// resolved at link time. The `extern fn` decls give elc the C prototype so the
// generated registry.c compiles cleanly under strict compilers (gcc>=14 / clang)
// that reject implicit function declarations. Signature arity must match the
// definition; return/param types are informational (all lower to el_val_t).
extern fn config(key: String) -> String          // El runtime builtin
extern fn read_installed() -> String              // install.el

// ── Config helpers ────────────────────────────────────────────────────────────

// registry_api_url returns the Gitea API base URL with no trailing slash.
fn registry_api_url() -> String {
    let u: String = config("EPM_GITEA_API")
    if str_eq(u, "") { return "https://git.neuralplatform.ai/api/v1" }
    let n: Int = str_len(u)
    if str_ends_with(u, "/") {
        return str_slice(u, 0, n - 1)
    }
    return u
}

// registry_org returns the Gitea org that hosts vessel repos.
fn registry_org() -> String {
    let o: String = config("EPM_REGISTRY_ORG")
    if str_eq(o, "") { return "neuron-technologies" }
    return o
}

// registry_token returns the Gitea auth token for publish operations.
fn registry_token() -> String {
    return config("EPM_TOKEN")
}

// ── Release lookups ───────────────────────────────────────────────────────────

// registry_find_release fetches a specific release by tag from Gitea.
// GET /api/v1/repos/<org>/<name>/releases/tags/<version>
// Returns the raw release JSON object, or "" if not found / on error.
fn registry_find_release(name: String, version: String) -> String {
    let url: String = registry_api_url() + "/repos/" + registry_org() + "/" + name + "/releases/tags/" + version
    let token: String = registry_token()
    let resp: String = ""
    if str_eq(token, "") {
        let resp = http_get(url)
    } else {
        let headers: String = "Authorization: token " + token
        let resp = http_get_with_headers(url, headers)
    }
    if str_eq(resp, "") { return "" }
    // Gitea returns 404 as an error JSON or empty — check for "id" field
    let release_id: String = json_get_string(resp, "id")
    if str_eq(release_id, "") { return "" }
    return resp
}

// registry_list_releases fetches all releases for a vessel from Gitea.
// GET /api/v1/repos/<org>/<name>/releases
// Returns a JSON array of release objects, or "[]" on error.
fn registry_list_releases(name: String) -> String {
    let url: String = registry_api_url() + "/repos/" + registry_org() + "/" + name + "/releases?limit=50"
    let token: String = registry_token()
    let resp: String = ""
    if str_eq(token, "") {
        let resp = http_get(url)
    } else {
        let headers: String = "Authorization: token " + token
        let resp = http_get_with_headers(url, headers)
    }
    if str_eq(resp, "") { return "[]" }
    // Verify it is an array
    let count: Int = json_array_len(resp)
    if count < 0 { return "[]" }
    return resp
}

// registry_latest_version returns the tag_name of the most recent release.
// Gitea returns releases sorted by creation date (newest first).
// Returns "" if there are no releases or the repo is missing.
fn registry_latest_version(name: String) -> String {
    let releases: String = registry_list_releases(name)
    let count: Int = json_array_len(releases)
    if count == 0 { return "" }
    let first: String = json_array_get(releases, 0)
    return json_get_string(first, "tag_name")
}

// registry_find_asset_url scans a release JSON object for the source tarball.
// Looks for an asset named "<name>-<version>.tar.gz".
// Falls back to zipball_url if no matching asset is found.
// Returns "" if the release JSON is empty.
fn registry_find_asset_url(release_json: String, name: String, version: String) -> String {
    if str_eq(release_json, "") { return "" }

    let target_asset: String = name + "-" + version + ".tar.gz"
    let assets_json: String = json_get_raw(release_json, "assets")
    let asset_count: Int = json_array_len(assets_json)

    let i: Int = 0
    while i < asset_count {
        let asset: String = json_array_get(assets_json, i)
        let asset_name: String = json_get_string(asset, "name")
        if str_eq(asset_name, target_asset) {
            return json_get_string(asset, "browser_download_url")
        }
        let i = i + 1
    }

    // Fall back to zipball_url from the release
    let zipball: String = json_get_string(release_json, "zipball_url")
    return zipball
}

// ── Public API ────────────────────────────────────────────────────────────────

// registry_find looks up a vessel in the Gitea registry.
// When version is "" the latest release is used.
//
// Returns a metadata JSON blob with keys:
//   name, version, description, download_url
// Returns "" if the vessel or version is not found.
fn registry_find(name: String, version: String) -> String {
    let ver: String = version
    if str_eq(ver, "") {
        let ver = registry_latest_version(name)
        if str_eq(ver, "") {
            return ""
        }
    }

    let release_json: String = registry_find_release(name, ver)
    if str_eq(release_json, "") { return "" }

    let description: String = json_get_string(release_json, "body")
    let download_url: String = registry_find_asset_url(release_json, name, ver)

    let esc_name: String = json_escape_string(name)
    let esc_ver: String  = json_escape_string(ver)
    let esc_desc: String = json_escape_string(description)
    let esc_url: String  = json_escape_string(download_url)

    return "{\"name\":\"" + esc_name + "\",\"version\":\"" + esc_ver + "\",\"description\":\"" + esc_desc + "\",\"download_url\":\"" + esc_url + "\"}"
}

// registry_list returns a JSON array of installed vessel metadata blobs.
// Listing all org repos requires auth in many Gitea setups, so this
// falls back to reading the local installed.json.
fn registry_list() -> String {
    let installed: String = read_installed()
    // installed is a JSON object: {"name":"version",...}
    // We return an empty array — callers should use installed_version() per name
    if str_eq(installed, "") { return "[]" }
    if str_eq(installed, "{}") { return "[]" }
    return "[]"
}

// ── Publish ───────────────────────────────────────────────────────────────────

// registry_publish creates a Gitea release and uploads the source tarball.
//
// Steps:
//   1. Build a tarball of the current directory (excluding .epm/, .git/)
//   2. POST /repos/<org>/<name>/releases to create the release
//   3. POST /repos/<org>/<name>/releases/<id>/assets to upload the tarball
//
// Returns the release ID string on success, "" on failure.
fn registry_publish(name: String, version: String, description: String, entry: String, deps_json: String) -> String {
    let token: String = registry_token()
    if str_eq(token, "") {
        println("epm: error: EPM_TOKEN is required for publishing")
        return ""
    }

    let tarball: String = "/tmp/epm-publish-" + name + "-" + version + ".tar.gz"
    let asset_name: String = name + "-" + version + ".tar.gz"

    // Build the source tarball from the current directory
    let tar_cmd: String = "tar -czf " + tarball + " --exclude='.epm' --exclude='.git' --exclude='*.tar.gz' ."
    let tar_ret: Int = exec_command(tar_cmd)
    if tar_ret != 0 {
        println("epm: error: failed to create source tarball (exit " + native_int_to_str(tar_ret) + ")")
        return ""
    }

    // Build release body — include metadata as JSON comment
    let esc_desc: String = json_escape_string(description)
    let esc_entry: String = json_escape_string(entry)
    let body_text: String = description + "\n\n<!-- epm: {\"entry\":\"" + esc_entry + "\",\"deps\":" + deps_json + "} -->"
    let esc_body: String = json_escape_string(body_text)
    let esc_tag: String = json_escape_string(version)
    let esc_name_field: String = json_escape_string(name + " " + version)

    let release_body: String = "{\"tag_name\":\"" + esc_tag + "\",\"name\":\"" + esc_name_field + "\",\"body\":\"" + esc_body + "\",\"draft\":false,\"prerelease\":false}"

    let api_base: String = registry_api_url()
    let org: String = registry_org()
    let releases_url: String = api_base + "/repos/" + org + "/" + name + "/releases"
    let auth_header: String = "Authorization: token " + token

    let resp: String = http_post_json_with_headers(releases_url, auth_header, release_body)
    if str_eq(resp, "") {
        println("epm: error: failed to create release on Gitea (no response)")
        return ""
    }

    let err_msg: String = json_get_string(resp, "message")
    if !str_eq(err_msg, "") {
        println("epm: error from Gitea: " + err_msg)
        return ""
    }

    let release_id: String = json_get_string(resp, "id")
    if str_eq(release_id, "") {
        println("epm: error: Gitea release response missing id")
        return ""
    }

    // Upload the tarball as a release asset via curl (multipart form)
    let upload_url: String = api_base + "/repos/" + org + "/" + name + "/releases/" + release_id + "/assets?name=" + asset_name
    let curl_cmd: String = "curl -s -X POST -H \"Authorization: token " + token + "\" -H \"Content-Type: application/octet-stream\" --data-binary @" + tarball + " \"" + upload_url + "\""
    let upload_resp: String = exec(curl_cmd)

    // Clean up tarball
    let rm_ret: Int = exec_command("rm -f " + tarball)

    if str_eq(upload_resp, "") {
        println("epm: warning: asset upload returned no response (release " + release_id + " was created)")
    }

    return release_id
}
