#!/usr/bin/env -S cmake -P

# SPDX-FileCopyrightText: Copyright 2026 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later

cmake_minimum_required(VERSION 3.31)

# TODO: Account for CPMConfig.cmake
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMakeModules)
set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR})

include(CPMUtil)

# Parse the JSON object for a given key.
macro(parse_key key)
    get_json_object(${key})
    set(JSON_NAME ${key})
    parse_object(${object})
endmacro()

# Get a package's effective URL, for an already parsed object
function(get_package_url_object out)
    if (${url})
        set(${out} "${url}")
    else()
        get_package_url(URL_OUT "${out}"
            GIT_HOST "${git_host}"
            REPO "${repo}"
            VERSION "${version}"
            ARTIFACT "${artifact}"
            PACKAGE "${package}")
    endif()

    return(PROPAGATE ${out})
endfunction()

# Fetch a package from an already-parsed object.
function(fetch_package_object)
    set(optionArgs FORCE)
    cmake_parse_arguments(ARG "${optionArgs}" "" "" ${ARGN})

    if (${url})
        set(pkg_url "${url}")
    else()
        get_package_url(URL_OUT pkg_url
            GIT_HOST "${git_host}"
            REPO "${repo}"
            VERSION "${version}"
            ARTIFACT "${artifact}"
            PACKAGE "${package}")
    endif()

    if (DEFINED CACHE_PATH_OVERRIDE)
        set(cache_path ${CACHE_PATH_OVERRIDE})
    else()
        get_cache_path(${package} ${version} cache_path)
    endif()

    set(fetch_args
        URL "${pkg_url}"
        HASH "${hash}"
        PATH "${cache_path}"
        PATCHES ${patches})

    if(ARG_FORCE)
        list(APPEND fetch_args FORCE)
    endif()

    fetch_package(${fetch_args})
endfunction()

# Format the cpmfile. Requires one of: jq, python, perl
# If you don't have any of those, sorry not sorry.
# Maybe I should make a shell-based alternative.
function(format_cpmfile)
    # jq is the preferred formatter since it's the fastest
    cpm_find_program(JQ_EXECUTABLE jq)
    if (JQ_EXECUTABLE)
        set(command ${JQ_EXECUTABLE} --indent 4 -S .)
    else()
        # Python is simple and works
        find_package(Python 3.5 COMPONENTS Interpreter QUIET)
        if (Python_FOUND)
            set(command ${Python_EXECUTABLE} -m json.tool
                --indent 4 --sort-keys)
        else()
            # json_pp (part of perl) also works well
            cpm_find_program(JSONPP_EXECUTABLE json_pp)
            if (JSONPP_EXECUTABLE)
                set(json_opts "indent" "indent_length=4" "canonical"
                    "space_after=1" "space_before=0")
                string(JOIN "," json_opts_str ${json_opts})

                set(command ${JSONPP_EXECUTABLE} -f json -t json -json_opt
                    "${json_opts_str}")
            else()
                fatal("Fatal: could not find one of jq, Python, or perl"
                    "(json_pp). Install one of these packages to use"
                    "CPMUtil's tooling. If they ARE installed, your"
                    "CMake installation is broken.")
            endif()
        endif()
    endif()

    get_cpmfile_path(file)
    mktempdir(TMP)
    set(tmp_file ${TMP}/cpmfile.json)

    execute_process(COMMAND ${command}
        INPUT_FILE ${file}
        OUTPUT_FILE ${tmp_file})

    # TODO: error handling, mv, cp?
    file(COPY_FILE ${tmp_file} ${file})

    file(REMOVE_RECURSE ${TMP})
endfunction()

# Computes expected SHA512 hash of a package
function(get_package_hash url out)
    mktempdir(TMP)

    get_filename_component(filename ${url} NAME)
    set(file ${TMP}/${filename})

    cpm_download("${url}" "${file}")
    file(SHA512 ${file} ${out})

    file(REMOVE_RECURSE ${TMP})
    return(PROPAGATE ${out})
endfunction()

# Download and put the content into a variable.
function(cpm_download_var url out)
    mktempdir(TMP)

    set(file ${TMP}/tmp)
    cpm_download("${url}" "${file}")
    file(READ ${file} ${out})

    file(REMOVE_RECURSE ${TMP})
    return(PROPAGATE ${out})
endfunction()

# Check if a URL request succeeds without actually saving anything
function(cpm_url_exists url out)
    foreach(i RANGE 5)
        file(DOWNLOAD "${url}" STATUS ret LOG log TIMEOUT 10)
        list(GET ret 0 code)

        if (code EQUAL 0)
            set(${out} TRUE)
            break()
        else()
            if (log MATCHES "HTTP/[0-9.]+ (429|403)")
                sleep(5)
                continue()
            endif()

            set(${out} FALSE)
            break()
        endif()
    endforeach()

    return(PROPAGATE ${out})
endfunction()

# Get latest tag for a package.
# Requires an already-parsed object
function(get_latest_tag out)
    # TODO: Ci packages
    if (NOT repo OR ci)
        set(${out} null)
        return(PROPAGATE ${out})
    endif()

    # first determine if this is a tag or not
    if (git_host STREQUAL github.com)
        set(api "https://api.github.com/repos/${repo}")
        set(check_endpoint "/git/refs/tags")
    else()
        set(api "https://${git_host}/api/v1/repos/${repo}")
        set(check_endpoint "/tags")
    endif()

    # artifacts must only check releases
    if ("${artifact}" STREQUAL "")
        set(json_key name)
        set(endpoint "/tags")
    else()
        set(json_key tag_name)
        set(endpoint "/releases")
        set(check_endpoint "/releases/tags")
    endif()

    cpm_url_exists("${api}${check_endpoint}/${version}" is_tag)

    if (NOT is_tag)
        set(${out} null)
        return(PROPAGATE ${out})
    endif()

    # strip out prefixes e.g. boost-, openssl-, v
    # NOTE: subrels e.g. -1 at the end may cause issues.
    string(REGEX REPLACE "[^0-9.-]" "" t_numeric_version "${version}")
    string(REGEX REPLACE "-$|^-" "" t_numeric_version "${numeric_version}")

    # now api req
    cpm_download_var("${api}${endpoint}" tags)

    string(JSON len LENGTH ${tags})
    math(EXPR last_index "${len} - 1")

    set(greatest_version_numeric ${t_numeric_version})
    set(greatest_version ${version})

    foreach(i RANGE ${last_index})
        string(JSON tag_obj GET "${tags}" ${i})
        string(JSON tag_name GET "${tag_obj}" ${json_key})

        # same as above
        string(REGEX REPLACE "[^0-9.-]" "" numeric_tag "${tag_name}")
        string(REGEX REPLACE "-$|^-" "" numeric_tag "${numeric_tag}")

        if (numeric_tag VERSION_GREATER_EQUAL greatest_version_numeric)
            set(greatest_version_numeric ${numeric_tag})
            set(greatest_version ${tag_name})
        endif()
    endforeach()

    # numeric version replacement
    if (numeric_version)
        set(${out} ${greatest_version_numeric})
    else()
        set(${out} ${greatest_version})
    endif()

    return(PROPAGATE ${out})
endfunction()

# TODO(crueter): Combine these

# Update hash and version of a package
# Outputs the updated object
function(modify_package object version hash out)
    string(JSON new_object SET "${object}" hash "\"${hash}\"")
    string(JSON new_object SET "${new_object}" version "\"${version}\"")

    set(${out} "${new_object}")
    return(PROPAGATE ${out})
endfunction()

# Update hash and numeric_version of a package
# Outputs the updated object
function(modify_package_numeric object version hash out)
    string(JSON new_object SET "${object}" hash "\"${hash}\"")
    string(JSON new_object SET "${new_object}" numeric_version "\"${version}\"")

    set(${out} "${new_object}")
    return(PROPAGATE ${out})
endfunction()


function(get_cpmfile_keys out)
    get_cpmfile_content(object)

    string(JSON len LENGTH ${object})

    math(EXPR last_index "${len} - 1")
    foreach(i RANGE ${last_index})
        string(JSON key MEMBER ${object} ${i})
        list(APPEND ${out} ${key})
    endforeach()

    return(PROPAGATE ${out})
endfunction()

function(parse_script_args out)
    if(PRINT_USAGE)
        usage()
        cmake_language(EXIT 0)
    endif()

    if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT CMAKE_ARGC)
        set(${out} "" PARENT_SCOPE)
        return()
    endif()

    set(found_script FALSE)
    set(idx 0)

    get_filename_component(script_name "${CMAKE_SCRIPT_MODE_FILE}" NAME)

    while(idx LESS CMAKE_ARGC)
        if(found_script)
            set(arg "${CMAKE_ARGV${idx}}")
            if(NOT arg STREQUAL "--")
                list(APPEND positional_args ${arg})
            endif()
        elseif(CMAKE_ARGV${idx} STREQUAL CMAKE_SCRIPT_MODE_FILE)
            set(found_script TRUE)
        else()
            get_filename_component(arg_name "${CMAKE_ARGV${idx}}" NAME)
            if(arg_name STREQUAL script_name)
                set(found_script TRUE)
            endif()
        endif()
        math(EXPR idx "${idx} + 1")
    endwhile()

    if(ALL_PACKAGES AND NOT NO_ALL)
        get_cpmfile_keys(all_keys)
        if(NO_CI)
            set(filtered_keys)
            foreach(key ${all_keys})
                parse_key(${key})
                if(NOT ci)
                    list(APPEND filtered_keys ${key})
                endif()
            endforeach()
            set(positional_args ${filtered_keys})
        else()
            set(positional_args ${all_keys})
        endif()
    endif()

    set(${out} ${positional_args} PARENT_SCOPE)
endfunction()

# Convert a CMake list to a JSON array
function(list_to_array list out)
    set(${out} "[]")
    set(idx 0)
    foreach(elem ${list})
        string(JSON ${out} SET ${${out}} "${idx}" "\"${elem}\"")
        math(EXPR idx "${idx} + 1")
    endforeach()

    return(PROPAGATE ${out})
endfunction()