/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 *​ Copyright (c) 2025 The FreeBSD Foundation
 *​
 *​ Portions of this software were developed by
 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
 * the FreeBSD Foundation
 */

#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "serialize.h"
#include "core.h"
#include "software.h"
#include "simplelicensing.h"

/*
 * !doc
 *
 * .. c:function:: spdxtool_core_agent_t *spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
 *
 *    Create new /Core/Agent struct
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param char *creation_info_id: CreationInfo spdxId
 *    :param char *name: Name of agent
 *    :return: NULL if some problem occurs and Agent struct if not
 */
spdxtool_core_agent_t *
spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
{
	if (!client || !creation_info_id || !name)
		return NULL;

	spdxtool_core_agent_t *agent = calloc(1, sizeof(spdxtool_core_agent_t));
	if (!agent)
		goto err;

	agent->type = "Agent";

	char *spdx_id_name = strdup(name);
	if (!spdx_id_name)
		goto err;

	spdxtool_util_string_correction(spdx_id_name);

	agent->spdx_id = spdxtool_util_get_spdx_id_string(client, agent->type, spdx_id_name);
	free(spdx_id_name);

	agent->creation_info = strdup(creation_info_id);
	agent->name = strdup(name);

	if (!agent->spdx_id || !agent->creation_info || !agent->name)
		goto err;

	return agent;

err:
	pkgconf_error(client, "spdxtool_core_agent_new: out of memory");
	spdxtool_core_agent_free(agent);
	return NULL;
}

/*
 * !doc
 *
 * .. c:function:: void spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
 *
 *    Free /Core/Agent struct
 *
 *    :param spdxtool_core_agent_t *agent: Agent struct to be freed.
 *    :return: nothing
 */
void
spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
{
	if (!agent)
		return;

	free(agent->creation_info);
	free(agent->spdx_id);
	free(agent->name);

	free(agent);
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
 *
 *    Serialize /Core/Agent struct to a JSON value tree.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const spdxtool_core_agent_t *agent: Agent struct to be serialized.
 *    :return: spdxtool_serialize_value_t * representing the Agent object.
 */
spdxtool_serialize_value_t *
spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
{
	spdxtool_serialize_value_t *ret = NULL;
	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
	if (!object_list)
		goto err;

	if (!(spdxtool_serialize_object_add_string(object_list, "type", agent->type) &&
		spdxtool_serialize_object_add_string(object_list, "creationInfo", agent->creation_info) &&
		spdxtool_serialize_object_add_string(object_list, "spdxId", agent->spdx_id) &&
		spdxtool_serialize_object_add_string(object_list, "name", agent->name)))
	{
		goto err;
	}

	ret = spdxtool_serialize_value_object(object_list);
	object_list = NULL;

err:
	if (!ret)
		pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory");

	spdxtool_serialize_object_list_free(object_list);
	return ret;
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_core_tool_t *spdxtool_core_tool_new(pkgconf_client_t *client, const char *creation_info_id, const char *short_name, const char *name)
 *
 *    Create new /Core/Tool struct
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param char *creation_info_id: CreationInfo spdxId
 *    :param char *short_name Shorter name for tool used in SPDX id (default: 'spdxtool')
 *    :param char *name: Long name of tool (default: 'spdxtool')
 *    :return: NULL if some problem occurs and Tool struct if not
 */
spdxtool_core_tool_t *
spdxtool_core_tool_new(pkgconf_client_t *client, const char *creation_info_id, const char *short_name, const char *name)
{
	if (!client || !creation_info_id || !name)
		return NULL;

	spdxtool_core_tool_t *tool = calloc(1, sizeof(spdxtool_core_tool_t));
	if (!tool)
		goto err;

	tool->type = "Tool";

	char *spdx_id_name = strdup(short_name);
	if (!spdx_id_name)
		goto err;

	spdxtool_util_string_correction(spdx_id_name);

	tool->spdx_id = spdxtool_util_get_spdx_id_string(client, tool->type, spdx_id_name);
	free(spdx_id_name);

	tool->creation_info = strdup(creation_info_id);
	tool->name = strdup(name);

	if (!tool->spdx_id || !tool->creation_info || !tool->name)
		goto err;

	return tool;

	err:
	pkgconf_error(client, "spdxtool_core_agent_new: out of memory");
	spdxtool_core_tool_free(tool);
	return NULL;
}

/*
 * !doc
 *
 * .. c:function:: void spdxtool_core_tool_free(spdxtool_core_tool_t *tool)
 *
 *    Free /Core/Tool struct
 *
 *    :param spdxtool_core_tool_t *tool: Tool struct to be freed.
 *    :return: nothing
 */
void
spdxtool_core_tool_free(spdxtool_core_tool_t *tool)
{
	if (!tool)
		return;

	free(tool->creation_info);
	free(tool->spdx_id);
	free(tool->name);

	free(tool);
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_tool_to_object(pkgconf_client_t *client, const spdxtool_core_tool_t *tool)
 *
 *    Serialize /Core/Tool struct to a JSON value tree.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const spdxtool_core_tool_t *tool: Tool struct to be serialized.
 *    :return: spdxtool_serialize_value_t * representing the Agent object.
 */
spdxtool_serialize_value_t *
spdxtool_core_tool_to_object(pkgconf_client_t *client, const spdxtool_core_tool_t *tool)
{
	spdxtool_serialize_value_t *ret = NULL;
	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
	if (!object_list)
		goto err;

	if (!(spdxtool_serialize_object_add_string(object_list, "type", tool->type) &&
		spdxtool_serialize_object_add_string(object_list, "creationInfo", tool->creation_info) &&
		spdxtool_serialize_object_add_string(object_list, "spdxId", tool->spdx_id) &&
		spdxtool_serialize_object_add_string(object_list, "name", tool->name)))
	{
		goto err;
	}

	ret = spdxtool_serialize_value_object(object_list);
	object_list = NULL;

	err:
	if (!ret)
		pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory");

	spdxtool_serialize_object_list_free(object_list);
	return ret;
}


/*
 * !doc
 *
 * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time)
 *
 *    Create new /Core/CreationInfo struct
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const char *agent_id: Agent spdxId
 *    :param const char *id: Id for creation info
 *    :param const char *time: If NULL current time is used if not then
 *                             this time string is used. Time string should be
 *                             in ISO8601 format: YYYY-MM-DDTHH:MM:SSZ
 *    :return: NULL if some problem occurs and CreationInfo struct if not
 */
spdxtool_core_creation_info_t *
spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *tool_id, const char *id, const char *time)
{
	if (!client || !agent_id || !id || !tool_id)
		return NULL;

	spdxtool_core_creation_info_t *creation = calloc(1, sizeof(spdxtool_core_creation_info_t));
	if (!creation)
		goto err;

	creation->type = "CreationInfo";
	creation->created_using = "pkgconf spdxtool";
	creation->id = strdup(id);
	creation->created = time ? strdup(time) : spdxtool_util_get_current_iso8601_time();
	creation->created_by = strdup(agent_id);
	creation->created_using = strdup(tool_id);
	creation->spec_version = strdup(spdxtool_util_get_spdx_version(client));

	if (!creation->id || !creation->created || !creation->created_by || !creation->created_using | !creation->spec_version)
		goto err;

	return creation;

err:
	pkgconf_error(client, "spdxtool_core_creation_info_new: out of memory");
	spdxtool_core_creation_info_free(creation);
	return NULL;
}

/*
 * !doc
 *
 * .. c:function:: void spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
 *
 *    Free /Core/CreationInfo struct
 *
 *    :param spdxtool_core_creation_info_t *creation: CreationInfo struct to be freed.
 *    :return: nothing
 */
void
spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
{
	if (!creation)
		return;

	free(creation->id);
	free(creation->created);
	free(creation->created_by);
	free(creation->created_using);
	free(creation->spec_version);

	free(creation);
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
 *
 *    Serialize /Core/CreationInfo struct to a JSON value tree.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const spdxtool_core_creation_info_t *creation: CreationInfo struct to be serialized.
 *    :return: spdxtool_serialize_value_t * representing the CreationInfo object.
 */
spdxtool_serialize_value_t *
spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
{
	spdxtool_serialize_value_t *ret = NULL;
	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
	if (!object_list)
		goto err;

	spdxtool_serialize_array_t *created_by = spdxtool_serialize_array_new();
	if (!created_by)
		goto err;

	if (!spdxtool_serialize_array_add_string(created_by, creation->created_by))
	{
		spdxtool_serialize_array_free(created_by);
		goto err;
	}

	spdxtool_serialize_array_t *created_using = spdxtool_serialize_array_new();
	if (!created_using)
	{
		spdxtool_serialize_array_free(created_by);
		goto err;
	}

	if (!spdxtool_serialize_array_add_string(created_using, creation->created_using))
	{
		spdxtool_serialize_array_free(created_by);
		spdxtool_serialize_array_free(created_using);
		goto err;
	}

	if (!(spdxtool_serialize_object_add_string(object_list, "type", creation->type) &&
		spdxtool_serialize_object_add_string(object_list, "@id", creation->id) &&
		spdxtool_serialize_object_add_string(object_list, "created", creation->created)))
	{
		/* created_by or created_using has not been handed to the object list yet */
		spdxtool_serialize_array_free(created_by);
		spdxtool_serialize_array_free(created_using);
		goto err;
	}

	/* object_add_array takes ownership of created_by, freeing it on failure */
	if (!spdxtool_serialize_object_add_array(object_list, "createdBy", created_by))
	{
		spdxtool_serialize_array_free(created_using);
		goto err;
	}

	/* object_add_array takes ownership of created_using, freeing it on failure */
	if (!(spdxtool_serialize_object_add_array(object_list, "createUsing", created_using) &&
		spdxtool_serialize_object_add_string(object_list, "specVersion", creation->spec_version)))
	{
		goto err;
	}

	ret = spdxtool_serialize_value_object(object_list);
	object_list = NULL;

err:
	if (!ret)
		pkgconf_error(client, "spdxtool_core_creation_info_to_object: out of memory");

	spdxtool_serialize_object_list_free(object_list);
	return ret;
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent)
 *
 *    Create new /Core/SpdxDocument struct
 *    In SPDX Lite SBOM there can be only one SpdxDocument
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const char *spdx_id: Id of this SpdxDocument
 *    :param const char *creation_id: Id for creation info
 *    :param const char *agent_id: Agent for this document
 *    :return: NULL if some problem occurs and SpdxDocument struct if not
 */
spdxtool_core_spdx_document_t *
spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id)
{
	if (!client || !spdx_id || !creation_id || !agent_id)
		return NULL;

	spdxtool_core_spdx_document_t *spdx = calloc(1, sizeof(spdxtool_core_spdx_document_t));
	if (!spdx)
		goto err;

	spdx->type = "SpdxDocument";
	spdx->spdx_id = strdup(spdx_id);
	spdx->agent = strdup(agent_id);
	spdx->creation_info = strdup(creation_id);

	if (!spdx->spdx_id || !spdx->agent || !spdx->creation_info)
		goto err;

	return spdx;

err:
	pkgconf_error(client, "spdxtool_core_spdx_document_new: out of memory");
	spdxtool_core_spdx_document_free(spdx);
	return NULL;
}

/*
 * !doc
 *
 * .. c:function:: void spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
 *
 *    Free /Core/SpdxDocument struct
 *
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be freed.
 *    :return: nothing
 */
void
spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
{
	pkgconf_node_t *iter = NULL, *iter_next = NULL;

	if (!spdx)
		return;

	free(spdx->spdx_id);
	free(spdx->creation_info);
	free(spdx->agent);

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->rootElement.head, iter_next, iter)
	{
		spdxtool_software_sbom_t *sbom = iter->data;
		spdxtool_software_sbom_free(sbom);
		free(iter);
	}

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->element.head, iter_next, iter)
	{
		free(iter->data);
		free(iter);
	}

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->licenses.head, iter_next, iter)
	{
		spdxtool_simplelicensing_license_expression_t *expression = iter->data;
		spdxtool_simplelicensing_licenseExpression_free(expression);
		free(iter);
	}

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->relationships.head, iter_next, iter)
	{
		spdxtool_core_relationship_t *relationship = iter->data;
		spdxtool_core_relationship_free(relationship);
		free(iter);
	}

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->maintainers.head, iter_next, iter)
	{
		spdxtool_core_agent_t *maintainer = iter->data;
		spdxtool_core_agent_free(maintainer);
		free(iter);
	}

	PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->packages.head, iter_next, iter)
	{
		free(iter);
	}

	free(spdx);
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
 *
 *    Serialize /Core/SpdxDocument struct to a JSON value tree. This function
 *    should be called after all SBOMs and packages have been serialized so that
 *    the document's element and rootElement lists are fully populated.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be serialized.
 *    :return: spdxtool_serialize_value_t * representing the SpdxDocument object.
 */
spdxtool_serialize_value_t *
spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
{
	spdxtool_serialize_value_t *ret = NULL;
	spdxtool_serialize_object_list_t *object_list = NULL;
	spdxtool_serialize_array_t *root_element_array = NULL;
	spdxtool_serialize_array_t *element_array = NULL;

	object_list = spdxtool_serialize_object_list_new();
	if (!object_list)
		goto err;

	root_element_array = spdxtool_serialize_array_new();
	if (!root_element_array)
		goto err;

	pkgconf_node_t *iter = NULL;
	PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
	{
		spdxtool_software_sbom_t *sbom = iter->data;
		if (!spdxtool_serialize_array_add_string(root_element_array, sbom->spdx_id))
			goto err;
	}

	element_array = spdxtool_serialize_array_new();
	if (!element_array)
		goto err;

	if (!spdxtool_serialize_array_add_string(element_array, spdx->agent))
		goto err;

	PKGCONF_FOREACH_LIST_ENTRY(spdx->element.head, iter)
	{
		char *element_id = iter->data;
		if (!spdxtool_serialize_array_add_string(element_array, element_id))
			goto err;
	}

	PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
	{
		spdxtool_software_sbom_t *sbom = iter->data;
		char *pkg_spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId");
		if (!pkg_spdx_id)
			goto err;

		bool ok = spdxtool_serialize_array_add_string(element_array, sbom->spdx_id) &&
			spdxtool_serialize_array_add_string(element_array, pkg_spdx_id);
		free(pkg_spdx_id);

		if (!ok)
			goto err;
	}

	if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) &&
		spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) &&
		spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id)))
	{
		goto err;
	}

	/* object_add_array always takes ownership of the array (it is freed even on
	 * failure), so clear our reference before checking the result to avoid a
	 * double free at the error label.
	 */
	bool ok = spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array);
	root_element_array = NULL;
	if (!ok)
		goto err;

	ok = spdxtool_serialize_object_add_array(object_list, "element", element_array);
	element_array = NULL;
	if (!ok)
		goto err;

	ret = spdxtool_serialize_value_object(object_list);
	object_list = NULL;

err:
	if (!ret)
		pkgconf_error(client, "spdxtool_core_spdx_document_to_object: out of memory");

	spdxtool_serialize_object_list_free(object_list);
	spdxtool_serialize_array_free(root_element_array);
	spdxtool_serialize_array_free(element_array);
	return ret;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
 *
 *    Find out if specific license is already there.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
 *    :param const char *license: SPDX name of license
 *    :return: true is license is there and false if not
 */
bool
spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license)
{
	pkgconf_node_t *iter = NULL;
	spdxtool_simplelicensing_license_expression_t *expression = NULL;

	(void) client;

	if (!license || !spdx)
	{
		return false;
	}

	PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter)
	{
		expression = iter->data;
		if (!strcmp(expression->license_expression, license))
		{
			return true;
		}
	}

	return false;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
 *
 *    Add license to SpdxDocument and make sure that specific license is not already there.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
 *    :param const char *license: SPDX name of license
 *    :return: true on success, false on failure
 */
bool
spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
{
	if (!license || !spdx)
		return false;

	if (spdxtool_core_spdx_document_is_license(client, spdx, license))
		return true;

	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
	if (!node)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_license: out of memory");
		return false;
	}

	spdxtool_simplelicensing_license_expression_t *expression = spdxtool_simplelicensing_licenseExpression_new(client, license);
	if (!expression)
	{
		free(node);
		return false;
	}

	if (!spdxtool_core_spdx_document_add_element(client, spdx, expression->spdx_id))
	{
		spdxtool_simplelicensing_licenseExpression_free(expression);
		free(node);
		return false;
	}

	pkgconf_node_insert_tail(node, expression, &spdx->licenses);
	return true;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
 *
 *    Add element spdxId to SpdxDocument
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
 *    :param char *element: spdxId of element
 *    :return: true on success, false on failure
 */
bool
spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
{
	if (!element || !spdx)
		return false;

	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
	if (!node)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
		return false;
	}

	char *nelement = strdup(element);
	if (!nelement)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
		free(node);
		return false;
	}

	pkgconf_node_insert_tail(node, nelement, &spdx->element);
	return true;
}

/*
 * !doc
 *
 * .. c:function:: const char *spdxtool_core_spdx_document_add_maintainer(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *name)
 *
 *    Register a package maintainer as an Agent and add it to the SpdxDocument so that
 *    packages may reference it via their ``suppliedBy`` field.  Maintainers are
 *    deduplicated by their spdxId, so a maintainer shared between packages is only
 *    emitted once.  The first time a maintainer is seen, its spdxId is also added to
 *    the document element list.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
 *    :param const char *name: Maintainer name as declared in the package.
 *    :return: the maintainer Agent spdxId (owned by the document) on success, NULL on failure
 */
const char *
spdxtool_core_spdx_document_add_maintainer(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *name)
{
	pkgconf_node_t *iter = NULL;

	if (!client || !spdx || !name)
		return NULL;

	spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, spdx->creation_info, name);
	if (!agent)
		return NULL;

	PKGCONF_FOREACH_LIST_ENTRY(spdx->maintainers.head, iter)
	{
		spdxtool_core_agent_t *existing = iter->data;
		if (!strcmp(existing->spdx_id, agent->spdx_id))
		{
			spdxtool_core_agent_free(agent);
			return existing->spdx_id;
		}
	}

	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
	if (!node)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_maintainer: out of memory");
		spdxtool_core_agent_free(agent);
		return NULL;
	}

	if (!spdxtool_core_spdx_document_add_element(client, spdx, agent->spdx_id))
	{
		free(node);
		spdxtool_core_agent_free(agent);
		return NULL;
	}

	pkgconf_node_insert_tail(node, agent, &spdx->maintainers);
	return agent->spdx_id;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
 *
 *    Add relationship rel to SpdxDocument
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
 *    :param spdxtool_core_relationship_t *relationship: relationship to add.
 *    :return: true on success, false on failure
 */
bool
spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
{
	if (!client || !spdx || !relationship)
		return false;

	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
	if (!node)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_relationship: out of memory");
		return false;
	}

	pkgconf_node_insert_tail(node, relationship, &spdx->relationships);
	return true;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
 *
 *    Register a package with the SpdxDocument for later serialization. The document
 *    does not take ownership of the package pointer; the package must outlive the
 *    document and will not be freed by spdxtool_core_spdx_document_free.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to register the package with.
 *    :param pkgconf_pkg_t *pkg: Package to register. Ownership is NOT transferred.
 *    :return: true on success, false on failure
 */
bool
spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
{
	if (!client || !spdx || !pkg)
		return false;

	pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
	if (!node)
	{
		pkgconf_error(client, "spdxtool_core_spdx_document_add_package: out of memory");
		return false;
	}

	pkgconf_node_insert_tail(node, pkg, &spdx->packages);
	return true;
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_core_relationship_t *spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, const char *to, const char *relationship_type)
 *
 *    Create new /Core/Relationship struct
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const char *creation_id: Id for creation info
 *    :param const char *spdx_id: Id of this SpdxDocument
 *    :param const char *from: from spdxId
 *    :param const char *to: to spdxId
 *    :param const char *relationship_type: These can be found on SPDX documentation
 *    :return: NULL if some problem occurs and SpdxDocument struct if not
 */
spdxtool_core_relationship_t *
spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type)
{
	if (!client || !creation_info_id || !spdx_id || !from || !to || !relationship_type)
		return NULL;

	spdxtool_core_relationship_t *relationship = calloc(1, sizeof(spdxtool_core_relationship_t));
	if (!relationship)
		goto err;

	relationship->type = "Relationship";
	relationship->creation_info = strdup(creation_info_id);
	relationship->spdx_id = strdup(spdx_id);
	relationship->from = strdup(from);
	relationship->relationship_type = strdup(relationship_type);

	if (!relationship->creation_info || !relationship->spdx_id || !relationship->from || !relationship->relationship_type)
		goto err;

	relationship->to = to;
	return relationship;

err:
	pkgconf_error(client, "spdxtool_core_relationship_new: out of memory");
	spdxtool_core_relationship_free(relationship);
	return NULL;
}

/*
 * !doc
 *
 * .. c:function:: bool spdxtool_core_relationship_set_scope(pkgconf_client_t *client, spdxtool_core_relationship_t *relationship, const char *scope)
 *
 *    Promote a relationship to a /Core/LifecycleScopedRelationship by attaching a lifecycle
 *    scope (for example, ``development`` for a private build dependency).  This is the SPDX 3.0
 *    representation of SPDX 2.x's ``*_DEPENDENCY_OF`` relationship variants.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param spdxtool_core_relationship_t *relationship: Relationship struct to scope.
 *    :param const char *scope: LifecycleScopeType value, e.g. "development".
 *    :return: true on success, false on failure
 */
bool
spdxtool_core_relationship_set_scope(pkgconf_client_t *client, spdxtool_core_relationship_t *relationship, const char *scope)
{
	if (!relationship || !scope)
		return false;

	char *scope_copy = strdup(scope);
	if (!scope_copy)
	{
		pkgconf_error(client, "spdxtool_core_relationship_set_scope: out of memory");
		return false;
	}

	free(relationship->scope);
	relationship->scope = scope_copy;
	relationship->type = "LifecycleScopedRelationship";

	return true;
}

/*
 * !doc
 *
 * .. c:function:: void spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
 *
 *    Free /Core/Relationship struct
 *
 *    :param spdxtool_core_relationship_t *relationship: Relationship struct to be freed.
 *    :return: nothing
 */
void
spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
{
	if (!relationship)
		return;

	free(relationship->spdx_id);
	free(relationship->creation_info);
	free(relationship->from);
	pkgconf_license_free(relationship->to);
	free(relationship->to);
	free(relationship->relationship_type);
	free(relationship->scope);

	free(relationship);
}

/*
 * !doc
 *
 * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
 *
 *    Serialize /Core/Relationship struct to a JSON value tree.
 *
 *    :param pkgconf_client_t *client: The pkgconf client being accessed.
 *    :param const spdxtool_core_relationship_t *relationship: Relationship struct to be serialized.
 *    :return: spdxtool_serialize_value_t * representing the Relationship object.
 */
spdxtool_serialize_value_t *
spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
{
	spdxtool_serialize_value_t *ret = NULL;
	spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
	if (!object_list)
		goto err;

	spdxtool_serialize_array_t *to = spdxtool_serialize_array_new();
	if (!to)
		goto err;

	pkgconf_node_t *node = NULL;
	PKGCONF_FOREACH_LIST_ENTRY(relationship->to->head, node)
	{
		const pkgconf_license_t *license = node->data;
		if (!spdxtool_serialize_array_add_string(to, license->data))
		{
			spdxtool_serialize_array_free(to);
			goto err;
		}
	}

	if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) &&
		spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) &&
		spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) &&
		spdxtool_serialize_object_add_string(object_list, "from", relationship->from)))
	{
		/* none of the above transfers ownership of `to` */
		spdxtool_serialize_array_free(to);
		goto err;
	}

	/* object_add_array always takes ownership of `to` (it is freed even on failure) */
	if (!spdxtool_serialize_object_add_array(object_list, "to", to))
		goto err;

	if (!spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type))
		goto err;

	if (relationship->scope != NULL &&
		!spdxtool_serialize_object_add_string(object_list, "scope", relationship->scope))
	{
		goto err;
	}

	ret = spdxtool_serialize_value_object(object_list);
	object_list = NULL;

err:
	if (!ret)
		pkgconf_error(client, "spdxtool_core_relationship_to_object: out of memory");

	spdxtool_serialize_object_list_free(object_list);
	return ret;
}
