"""Arcade Starter Tools for Hubspot_Marketing

DO NOT EDIT THIS MODULE DIRECTLY.

THIS MODULE WAS AUTO-GENERATED BY TRANSPILING THE API STARTER TOOL JSON DEFINITIONS
IN THE ../wrapper_tools DIRECTORY INTO PYTHON CODE. ANY CHANGES TO THIS MODULE WILL
BE OVERWRITTEN BY THE TRANSPILER.
"""

import asyncio
import json
from enum import Enum
from typing import Annotated, Any

import httpx
import jsonschema
from arcade_tdk import ToolContext, tool
from arcade_tdk.auth import OAuth2
from arcade_tdk.errors import RetryableToolError

from .request_body_schemas import REQUEST_BODY_SCHEMAS

# Retry configuration
INITIAL_RETRY_DELAY = 0.5  # seconds

HTTP_CLIENT = httpx.AsyncClient(
    timeout=httpx.Timeout(60.0, connect=10.0),
    limits=httpx.Limits(max_keepalive_connections=20, max_connections=100),
    transport=httpx.AsyncHTTPTransport(retries=3),
    http2=True,
    follow_redirects=True,
)


class ToolMode(str, Enum):
    """Mode for tools with complex request bodies."""

    GET_REQUEST_SCHEMA = "get_request_schema"
    EXECUTE = "execute"


def remove_none_values(data: dict[str, Any]) -> dict[str, Any]:
    return {k: v for k, v in data.items() if v is not None}


async def make_request(
    url: str,
    method: str,
    params: dict[str, Any] | None = None,
    headers: dict[str, Any] | None = None,
    content: str | None = None,
    data: dict[str, Any] | None = None,
    auth: tuple[str, str] | None = None,
    max_retries: int = 3,
) -> httpx.Response:
    """Make an HTTP request with retry logic for 5xx server errors."""
    for attempt in range(max_retries):
        try:
            response = await HTTP_CLIENT.request(
                url=url,
                auth=auth,
                method=method,
                params=params,
                headers=headers,
                content=content,
            )
            response.raise_for_status()
        except httpx.HTTPStatusError as e:
            # Only retry on 5xx server errors
            if e.response.status_code >= 500 and attempt < max_retries - 1:
                # Exponential backoff: 0.5s, 1s, 2s
                await asyncio.sleep(INITIAL_RETRY_DELAY * (2**attempt))
                continue
            # Re-raise for 4xx errors or if max retries reached
            raise
        except httpx.RequestError:
            # Don't retry request errors (network issues are handled by transport)
            raise
        else:
            return response

    # This should never be reached, but satisfies type checker
    raise httpx.RequestError("Max retries exceeded")


async def make_request_with_schema_validation(
    url: str,
    method: str,
    request_data: dict[str, Any],
    schema: dict[str, Any] | str,
    auth: tuple[str, str] | None = None,
    params: dict[str, Any] | None = None,
    headers: dict[str, Any] | None = None,
    max_retries: int = 3,
) -> httpx.Response:
    """Make an HTTP request with schema validation on format errors."""
    # Parse schema if it's a string, skip validation if parsing fails
    parsed_schema = None
    if isinstance(schema, str):
        try:
            parsed_schema = json.loads(schema)
        except Exception:
            # If schema parsing fails, just skip validation
            parsed_schema = None
    else:
        parsed_schema = schema

    try:
        response = await make_request(
            url=url,
            auth=auth,
            method=method,
            params=params,
            headers=headers,
            content=json.dumps(request_data),
            max_retries=max_retries,
        )
    except httpx.HTTPStatusError as e:
        # Only provide schema validation for format-related errors
        if e.response.status_code in (400, 422):
            api_error_details = f"API returned {e.response.status_code}: {e.response.text}"

            # Only run validation if we have a valid parsed schema
            if parsed_schema is not None:
                # Run validation to provide additional context
                is_valid, validation_error = validate_json_against_schema(
                    request_data, parsed_schema
                )

                if not is_valid:
                    # Schema validation found issues - additional context
                    additional_context = (
                        f"{api_error_details}\n\n"
                        f"Schema validation found the following issues:\n"
                        f"{validation_error}"
                    )
                else:
                    # Schema validation passed - just show API error
                    additional_context = api_error_details
            else:
                # No valid schema - just show API error
                additional_context = api_error_details

            raise RetryableToolError(
                message=(f"API request failed with validation error: {e.response.status_code}"),
                developer_message=api_error_details,
                additional_prompt_content=additional_context,
            ) from e
        else:
            # For non-validation errors, re-raise as-is
            raise
    else:
        return response


def validate_json_against_schema(
    json_data: dict[str, Any], schema: dict[str, Any]
) -> tuple[bool, str | None]:
    """Validate JSON data against an OpenAPI/JSON Schema.

    This provides full JSON Schema Draft 7 validation including:
    - Required fields, types, enums
    - Pattern validation (regex)
    - Format validation (email, uuid, date-time, etc.)
    - Min/max length and values
    - oneOf, anyOf, allOf
    - And all other JSON Schema features

    Args:
        json_data: The JSON data to validate
        schema: The JSON Schema to validate against

    Returns:
        Tuple of (is_valid, error_messages). If valid, error_messages is None.
        If invalid, error_messages contains all validation errors.
    """
    try:
        validator = jsonschema.Draft7Validator(
            schema, format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER
        )
        # Collect ALL validation errors
        errors = list(validator.iter_errors(json_data))
        if errors:
            # Format all errors with their paths
            error_messages = []
            for error in errors:
                error_path = ".".join(str(p) for p in error.path) if error.path else "root"
                error_messages.append(f"{error.message} at {error_path}")
            # Join all errors with newlines
            return False, "\n".join(error_messages)
        else:
            return True, None
    except jsonschema.SchemaError as e:
        return False, f"Invalid schema: {e.message}"
    except Exception as e:
        return False, f"Validation error: {e!s}"


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_campaign_spend_item(
    context: ToolContext,
    campaign_guid: Annotated[
        str, "Unique identifier for the marketing campaign to retrieve the spend item from."
    ],
    spend_item_identifier: Annotated[int, "Unique identifier for the spend item in the campaign."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}'.",
]:
    """Retrieve details of a specific campaign spend item.

    Use this tool to get information about a campaign spend item by providing its spendId and campaignGuid."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}".format(  # noqa: UP032
            campaignGuid=campaign_guid, spendId=spend_item_identifier
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def update_campaign_spend(
    context: ToolContext,
    campaign_guid: Annotated[str, "Unique identifier for the campaign."],
    spend_amount: Annotated[
        float,
        "The new amount value for the campaign spend. Specify this as a number representing the monetary value.",
    ],
    spend_identifier: Annotated[
        int, "Unique identifier for the spend item in the campaign to be updated."
    ],
    spend_item_name: Annotated[
        str,
        "The new name for the campaign spend item. This should be a descriptive string defining the spend item.",
    ],
    spend_order: Annotated[
        int,
        "The order or sequence number of the spend item within the campaign. It determines the priority or arrangement of the spend items.",
    ],
    spend_item_description: Annotated[
        str | None, "Details or notes about the campaign spend item."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}'.",
]:
    """Update a specific campaign spend item by ID.

    Use this tool to modify the details of a particular spend item in a marketing campaign by providing the campaign and spend identifiers."""
    request_data: Any = {
        "amount": spend_amount,
        "name": spend_item_name,
        "description": spend_item_description,
        "order": spend_order,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}".format(  # noqa: UP032
            campaignGuid=campaign_guid, spendId=spend_identifier
        ),
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def remove_campaign_spend_item(
    context: ToolContext,
    campaign_identifier: Annotated[str, "Unique identifier for the specific marketing campaign."],
    spend_item_id: Annotated[
        int, "Unique identifier for the specific spend item to be deleted from the campaign."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}'.",
]:
    """Deletes a specific campaign spend item by ID.

    Use this tool to delete a specific spend item from a marketing campaign using its ID. This is useful for managing and updating campaign expenses in HubSpot Marketing."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/spend/{spendId}".format(  # noqa: UP032
            campaignGuid=campaign_identifier, spendId=spend_item_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def update_campaign_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/campaigns/batch/update'."
]:
    """Update a batch of marketing campaigns efficiently.

    Use this tool to update multiple marketing campaigns in a single request. The tool handles up to 50 campaigns per request, and can reset property values when an empty string is provided.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_campaign_budget(
    context: ToolContext,
    budget_item_id: Annotated[int, "Unique identifier for the budget item to retrieve details."],
    campaign_guid: Annotated[
        str, "The unique identifier for the marketing campaign to access its budget details."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}'.",
]:
    """Retrieve details of a specific marketing campaign budget item.

    This tool is used to obtain information about a specific budget item within a marketing campaign by providing the campaign GUID and budget ID. Use this tool to access detailed budget data for analysis or reporting purposes."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}".format(  # noqa: UP032
            campaignGuid=campaign_guid, budgetId=budget_item_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def update_campaign_budget(
    context: ToolContext,
    budget_amount: Annotated[
        float,
        "The amount to set for the budget item. This is a numerical value representing the budget in the specified currency.",
    ],
    budget_id: Annotated[int, "Unique identifier for the budget item to be updated."],
    budget_item_name: Annotated[
        str,
        "The new name for the budget item. It should be a descriptive label that clearly identifies the budget item within the campaign.",
    ],
    campaign_guid: Annotated[
        str,
        "A unique identifier for the campaign. Use this to specify which campaign's budget item to update.",
    ],
    priority_order: Annotated[
        int, "Specify the order in which the budget item appears. Accepted values are integers."
    ],
    budget_item_description: Annotated[
        str | None,
        "A string to describe the budget item. Provide a brief explanation of what this budget is for.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}'.",
]:
    """Update a specific campaign budget item by ID.

    Use this tool to update a particular budget item in a marketing campaign by providing the campaign GUID and the budget ID."""
    request_data: Any = {
        "amount": budget_amount,
        "name": budget_item_name,
        "description": budget_item_description,
        "order": priority_order,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}".format(  # noqa: UP032
            campaignGuid=campaign_guid, budgetId=budget_id
        ),
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def delete_campaign_budget_item(
    context: ToolContext,
    budget_item_id: Annotated[int, "Unique identifier for the budget item to be deleted."],
    campaign_guid: Annotated[
        str, "Unique identifier for the campaign to delete the budget item from."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}'.",
]:
    """Delete a specific campaign budget item by ID.

    Use this tool to delete a specific budget item from a campaign using the campaignGuid and budgetId. Useful for managing or updating campaign budgets."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/budget/{budgetId}".format(  # noqa: UP032
            campaignGuid=campaign_guid, budgetId=budget_item_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def associate_asset_with_campaign(
    context: ToolContext,
    asset_id: Annotated[
        str,
        "The unique identifier for the asset to be associated with a campaign. This should be provided as a string.",
    ],
    asset_type: Annotated[
        str,
        "Specify the asset type to associate with the campaign. Allowed values are FORM, OBJECT_LIST, and EXTERNAL_WEB_URL.",
    ],
    campaign_unique_identifier: Annotated[
        str,
        "Unique identifier for the campaign, formatted as a UUID. Essential for associating an asset with the correct campaign.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}/{assetId}'.",
]:
    """Associate a specified asset with a HubSpot campaign.

    This tool is used to link various asset types such as forms, lists, or emails with a specific HubSpot campaign. It's useful for managing and organizing marketing efforts across different platforms."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}/{assetId}".format(  # noqa: UP032
            campaignGuid=campaign_unique_identifier, assetType=asset_type, assetId=asset_id
        ),
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def disassociate_campaign_asset(
    context: ToolContext,
    asset_id: Annotated[
        str, "Unique identifier for the asset to be disassociated from the campaign."
    ],
    asset_type: Annotated[
        str,
        "Specify the type of asset to disassociate, limited to FORM, OBJECT_LIST, or EXTERNAL_WEB_URL.",
    ],
    campaign_unique_identifier: Annotated[
        str, "Unique identifier for the campaign, formatted as a UUID."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}/{assetId}'.",
]:
    """Disassociate an asset from a HubSpot marketing campaign.

    Use this tool to remove a specified asset from a marketing campaign. Supported asset types include Forms, Static lists, and External website pages."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}/{assetId}".format(  # noqa: UP032
            campaignGuid=campaign_unique_identifier, assetType=asset_type, assetId=asset_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_campaign_budget_details(
    context: ToolContext,
    campaign_unique_identifier: Annotated[
        str, "Unique identifier for the campaign, formatted as a UUID."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/budget/totals'.",
]:
    """Retrieve detailed budget and spend details for a campaign.

    This tool retrieves detailed information regarding the budget and spending for a specific marketing campaign, including total budget, total spend, and remaining budget. It's useful for understanding financial aspects of a campaign."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/budget/totals".format(  # noqa: UP032
            campaignGuid=campaign_unique_identifier
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def fetch_marketing_campaign_batches(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    fetch_start_date: Annotated[
        str | None,
        "Start date to fetch asset metrics, formatted as YYYY-MM-DD. Determines the period for asset metrics. Optional.  Only used when mode is 'execute'.",
    ] = None,
    end_date_for_asset_metrics: Annotated[
        str | None,
        "End date to fetch asset metrics, formatted as YYYY-MM-DD. If not provided, no asset metrics will be fetched.  Only used when mode is 'execute'.",
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return in the response. Ignored if values are empty, resulting in an empty properties map if not specified.  Only used when mode is 'execute'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/campaigns/batch/read'."
]:
    """Retrieve a batch of HubSpot marketing campaigns and assets.

    Use this tool to read a batch of marketing campaigns from HubSpot, along with their associated assets. The tool allows for processing up to 50 campaigns per request. Note that the campaigns returned may not be in the order requested, and duplicate campaign IDs will be handled by returning only unique entries.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["FETCHMARKETINGCAMPAIGNBATCHES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["FETCHMARKETINGCAMPAIGNBATCHES"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["FETCHMARKETINGCAMPAIGNBATCHES"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["FETCHMARKETINGCAMPAIGNBATCHES"],
        params=remove_none_values({
            "startDate": fetch_start_date,
            "endDate": end_date_for_asset_metrics,
            "properties": requested_properties,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_marketing_campaigns(
    context: ToolContext,
    campaign_name_filter: Annotated[
        str | None,
        "A substring to filter campaigns by name. Returns campaigns containing this substring. Optional: returns all if not provided.",
    ] = None,
    max_results_limit: Annotated[
        int | None, "Maximum number of campaigns to return (1-100). Default is 50."
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A cursor for pagination. If provided, results start after the given cursor. Example: NTI1Cg%3D%3D",
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "A list of properties to be included in the response. Comma-separate values are required. If any specified property is empty, it will be ignored.",
    ] = None,
    sort_by: Annotated[
        str | None,
        "Specify the field to sort results by. Use '-' to denote descending order. Options are hs_name, createdAt, updatedAt. Default is hs_name.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/campaigns/'."]:
    """Retrieve a page of marketing campaigns with optional filters.

    This tool allows retrieval of a page of marketing campaigns based on query parameters such as name filtering, sorting, and pagination. You can also control the properties included in the response."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/",
        method="GET",
        params=remove_none_values({
            "sort": sort_by,
            "after": pagination_cursor,
            "limit": max_results_limit,
            "name": campaign_name_filter,
            "properties": requested_properties,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def create_marketing_campaign(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/campaigns/'."]:
    """Create a marketing campaign and retrieve its details.

    This tool creates a marketing campaign using provided properties and returns the campaign object, including essential details like campaignGuid and created properties.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGN"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGN"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGN"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGN"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_campaign_attribution_metrics(
    context: ToolContext,
    campaign_unique_identifier: Annotated[
        str,
        "Unique identifier for the campaign, formatted as a UUID. Required to retrieve specific campaign metrics.",
    ],
    report_end_date: Annotated[
        str | None,
        "Set the end date for the report data in YYYY-MM-DD format. Defaults to the current date if not specified.",
    ] = None,
    start_date_for_report: Annotated[
        str | None,
        "The start date for the report data, formatted as YYYY-MM-DD. Default is 2006-01-01.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/reports/metrics'.",
]:
    """Retrieve attribution metrics for a specific campaign.

    Retrieves key attribution metrics for a specified campaign, including sessions, new contacts, and influenced contacts. Use this tool to analyze campaign performance."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/reports/metrics".format(  # noqa: UP032
            campaignGuid=campaign_unique_identifier
        ),
        method="GET",
        params=remove_none_values({"startDate": start_date_for_report, "endDate": report_end_date}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def list_campaign_assets(
    context: ToolContext,
    asset_type: Annotated[
        str, "The type of asset to fetch for the campaign, e.g., 'email', 'webpage'."
    ],
    campaign_identifier: Annotated[str, "Unique identifier for the campaign in UUID format."],
    end_date_for_asset_metrics: Annotated[
        str | None,
        "End date to fetch asset metrics, formatted as YYYY-MM-DD. Used for fetching metrics of assets within a specified period. Optional parameter.",
    ] = None,
    maximum_results_limit: Annotated[
        str | None, "The maximum number of asset results to return. Default is 10."
    ] = None,
    metrics_start_date: Annotated[
        str | None,
        "Start date to fetch asset metrics, formatted as YYYY-MM-DD. Used to fetch metrics for a specified period. If not provided, no metrics will be fetched.",
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A cursor for pagination. If provided, results start after this cursor. Example: NTI1Cg%3D%3D",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}'.",
]:
    """Retrieve all assets of a specified type for a campaign.

    This tool lists all assets of a particular type within a specified marketing campaign. It can also fetch asset metrics if start and end dates are provided, allowing for detailed analysis of campaign performance."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/assets/{assetType}".format(  # noqa: UP032
            campaignGuid=campaign_identifier, assetType=asset_type
        ),
        method="GET",
        params=remove_none_values({
            "after": pagination_cursor,
            "limit": maximum_results_limit,
            "startDate": metrics_start_date,
            "endDate": end_date_for_asset_metrics,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def archive_campaigns_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/campaigns/batch/archive'."
]:
    """Delete a batch of marketing campaigns.

    Use this tool to delete multiple marketing campaigns in a single batch request (up to 50 campaigns). The response will indicate if the request was processed, but it won't specify the success of individual deletions.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ARCHIVECAMPAIGNSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["ARCHIVECAMPAIGNSBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["ARCHIVECAMPAIGNSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECAMPAIGNSBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.revenue.read"]))
async def get_campaign_revenue_report(
    context: ToolContext,
    campaign_unique_identifier: Annotated[
        str, "Unique identifier for the campaign, formatted as a UUID."
    ],
    attribution_model: Annotated[
        str | None,
        "The revenue attribution model to be used. Allowed values: LINEAR, FIRST_INTERACTION, LAST_INTERACTION, FULL_PATH, U_SHAPED, W_SHAPED, TIME_DECAY, J_SHAPED, INVERSE_J_SHAPED. Defaults to LINEAR.",
    ] = None,
    report_end_date: Annotated[
        str | None,
        "End date for the report data in YYYY-MM-DD format. Defaults to the current date.",
    ] = None,
    report_start_date: Annotated[
        str | None,
        "The start date for the report data in the format YYYY-MM-DD. Default is 2006-01-01.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/reports/revenue'.",
]:
    """Fetch revenue attribution report for a specific campaign.

    This tool retrieves the revenue attribution report data for a specified marketing campaign using its unique campaign GUID."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/reports/revenue".format(  # noqa: UP032
            campaignGuid=campaign_unique_identifier
        ),
        method="GET",
        params=remove_none_values({
            "attributionModel": attribution_model,
            "startDate": report_start_date,
            "endDate": report_end_date,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def create_marketing_campaign_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/campaigns/batch/create'."
]:
    """Create a batch of marketing campaigns in HubSpot.

    This tool is used to batch create up to 50 marketing campaigns in HubSpot Marketing. The campaigns in the response may not be in the same order as they were submitted in the request.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGNBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGNBATCH"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGNBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMARKETINGCAMPAIGNBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def add_campaign_budget_item(
    context: ToolContext,
    budget_amount: Annotated[
        float,
        "The monetary amount for the budget item. It should be a numeric value denoting the budget amount to add to the campaign.",
    ],
    budget_item_name: Annotated[
        str,
        "Name of the budget item to be added to the campaign. It should be descriptive and concise.",
    ],
    budget_item_order: Annotated[
        int, "The position or priority of the budget item in the list. Provide as an integer."
    ],
    campaign_id: Annotated[str, "Unique identifier for the campaign in HubSpot Marketing."],
    budget_item_description: Annotated[
        str | None,
        "A brief description of the budget item being added to the campaign. This can include details about what the budget will be used for and any other relevant information.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/campaigns/{campaignGuid}/budget'.",
]:
    """Add a new budget item to a marketing campaign.

    Use this tool to add a budget item to an existing campaign in HubSpot Marketing. Ideal for updating campaign financials."""
    request_data: Any = {
        "amount": budget_amount,
        "name": budget_item_name,
        "description": budget_item_description,
        "order": budget_item_order,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/budget".format(  # noqa: UP032
            campaignGuid=campaign_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def get_campaign_details(
    context: ToolContext,
    campaign_guid: Annotated[str, "Unique identifier for the campaign, formatted as a UUID."],
    end_date_for_asset_metrics: Annotated[
        str | None,
        "End date to fetch asset metrics, formatted as YYYY-MM-DD. If omitted, no metrics will be retrieved.",
    ] = None,
    metrics_start_date: Annotated[
        str | None,
        "Start date for fetching asset metrics. Format as YYYY-MM-DD. Metrics are only retrieved if both start and end dates are provided.",
    ] = None,
    properties_list: Annotated[
        list[str] | None,
        "List specific properties to return in the response. Leave empty for an empty properties map.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}'."
]:
    """Retrieve details and metrics for a specific marketing campaign.

    Use this tool to get detailed information about a marketing campaign using its campaignGuid. It also provides information about associated assets, and if start and end dates are given, metrics for those assets."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}".format(  # noqa: UP032
            campaignGuid=campaign_guid
        ),
        method="GET",
        params=remove_none_values({
            "startDate": metrics_start_date,
            "endDate": end_date_for_asset_metrics,
            "properties": properties_list,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def delete_marketing_campaign(
    context: ToolContext,
    campaign_guid: Annotated[str, "Unique identifier for the campaign, formatted as a UUID."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/campaigns/{campaignGuid}'.",
]:
    """Delete a specified marketing campaign.

    Use this tool to delete a marketing campaign from the system by providing the campaign's GUID. A 204 No Content response is returned, indicating the request was processed, regardless of whether the campaign existed."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}".format(  # noqa: UP032
            campaignGuid=campaign_guid
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def update_campaign_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    campaign_uuid: Annotated[
        str | None,
        "Unique identifier for the campaign, formatted as a UUID.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/marketing/v3/campaigns/{campaignGuid}'."
]:
    """Update properties of a HubSpot marketing campaign.

    Use this tool to perform a partial update of a marketing campaign in HubSpot by providing the campaignGuid and the new property values. Note that only existing, mutable properties can be updated; read-only and non-existent properties will cause an error. Passing an empty string will reset a property's value.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNPROPERTIES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not campaign_uuid:
        missing_params.append(("campaign_uuid", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNPROPERTIES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNPROPERTIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}".format(  # noqa: UP032
            campaignGuid=campaign_uuid
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECAMPAIGNPROPERTIES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.read"]))
async def fetch_campaign_contact_ids(
    context: ToolContext,
    campaign_identifier: Annotated[str, "Unique identifier for the campaign formatted as a UUID."],
    contact_type_filter: Annotated[
        str,
        "Specifies the type of metric for filtering contacts. Options: contactFirstTouch, contactLastTouch, influencedContacts.",
    ],
    contact_fetch_limit: Annotated[
        int | None, "Specify the maximum number of contact IDs to retrieve, with a default of 100."
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A string cursor for pagination to start results after the given point. Example: NTI1Cg%3D%3D",
    ] = None,
    report_end_date: Annotated[
        str | None,
        "The end date for the report data in YYYY-MM-DD format. Defaults to the current date if not specified.",
    ] = None,
    start_date: Annotated[
        str | None,
        "The start date for the report data in YYYY-MM-DD format. Default is 2006-01-01.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/campaigns/{campaignGuid}/reports/contacts/{contactType}'.",
]:
    """Fetch contact IDs for a specific campaign and contact type.

    Use this tool to obtain the contact IDs associated with a particular marketing campaign and specified contact type in HubSpot. It is useful for analyzing campaign reach and engagement with specific contact demographics."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/reports/contacts/{contactType}".format(  # noqa: UP032
            campaignGuid=campaign_identifier, contactType=contact_type_filter
        ),
        method="GET",
        params=remove_none_values({
            "startDate": start_date,
            "endDate": report_end_date,
            "limit": contact_fetch_limit,
            "after": pagination_cursor,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing.campaigns.write"]))
async def create_campaign_spend(
    context: ToolContext,
    campaign_id: Annotated[
        str, "Unique identifier for the marketing campaign to add the spend item to."
    ],
    spend_amount: Annotated[
        float,
        "The monetary value of the spend item to be added to the campaign. This should be a numerical value representing the amount in the currency used by the campaign.",
    ],
    spend_item_name: Annotated[
        str,
        "The name of the spend item to be created for the campaign. This should be a descriptive title or label for the expenditure.",
    ],
    spend_item_order: Annotated[
        int, "The order of the spend item in the campaign. Use an integer to specify the sequence."
    ],
    spend_description: Annotated[
        str | None,
        "A brief description of the campaign spend item, detailing the nature or purpose of the expenditure.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/campaigns/{campaignGuid}/spend'.",
]:
    """Create a new campaign spend item for a marketing campaign.

    This tool creates a new spend item for a specified marketing campaign. It should be called when you need to add or track expenditures related to a specific campaign."""
    request_data: Any = {
        "amount": spend_amount,
        "name": spend_item_name,
        "description": spend_description,
        "order": spend_item_order,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/campaigns/{campaignGuid}/spend".format(  # noqa: UP032
            campaignGuid=campaign_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def get_form_by_id(
    context: ToolContext,
    form_id: Annotated[
        str,
        "The unique identifier for the specific marketing form to retrieve. This ID is required to fetch the form details.",
    ],
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived forms."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/forms/{formId}_getById'."
]:
    """Retrieve a marketing form by its ID.

    Use this tool to get detailed information about a specific marketing form by providing its unique form ID."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/{formId}".format(formId=form_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"archived": return_archived_only}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def update_hubspot_form(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubspot_form_id: Annotated[
        str | None,
        "The unique identifier for the HubSpot form to update. This ID specifies which form's fields will be replaced.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'put-/marketing/v3/forms/{formId}_replace'."
]:
    """Update all fields of a HubSpot form.

    Use this tool to update every field in a specified HubSpot form by replacing the current form definition.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTFORM"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not hubspot_form_id:
        missing_params.append(("hubspot_form_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTFORM"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTFORM"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/{formId}".format(formId=hubspot_form_id),  # noqa: UP032
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def archive_marketing_form(
    context: ToolContext,
    form_id: Annotated[
        str,
        "The ID of the form to archive in HubSpot. This is required to process the archive request.",
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/marketing/v3/forms/{formId}_archive'."
]:
    """Archive a marketing form in HubSpot.

    Use this tool to archive a form in HubSpot Marketing, preventing new submissions. The form will be permanently deleted after 3 months."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/{formId}".format(formId=form_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def update_hubspot_marketing_form(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    form_id: Annotated[
        str | None,
        "The unique identifier for the form to update. Must be provided to specify which form's components to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/marketing/v3/forms/{formId}_update'."
]:
    """Update components of a HubSpot marketing form.

    Use this tool to modify specific components of an existing marketing form in HubSpot by providing the form ID. Ideal for updating form details without creating a new form.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTMARKETINGFORM"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not form_id:
        missing_params.append(("form_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTMARKETINGFORM"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTMARKETINGFORM"]
                + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/{formId}".format(formId=form_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def list_hubspot_forms(
    context: ToolContext,
    included_form_types: Annotated[
        list[str] | None,
        "List of form types to include in results, e.g., 'hubspot', 'workflow', etc.",
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "The token indicating the cursor position for paginated results. Use `paging.next.after` from the previous response to fetch more results.",
    ] = None,
    results_per_page: Annotated[
        int | None, "Specify the maximum number of forms to retrieve per page."
    ] = None,
    return_only_archived_forms: Annotated[
        bool | None, "Set to true to return only archived forms in the results."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/forms/_getPage'."
]:
    """Retrieve a list of HubSpot marketing forms.

    Call this tool to obtain a list of marketing forms from HubSpot. It applies search filters if provided, otherwise returns the first 20 forms by default."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/",
        method="GET",
        params=remove_none_values({
            "after": paging_cursor_token,
            "limit": results_per_page,
            "archived": return_only_archived_forms,
            "formTypes": included_form_types,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["forms"]))
async def create_marketing_form(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/forms/_create'."
]:
    """Create a new marketing form in HubSpot.

    This tool is used to add a new form to HubSpot's marketing systems. It should be called whenever you need to create a new form for collecting marketing data.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMARKETINGFORM"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGFORM"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGFORM"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/forms/",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_email_statistics(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/emails/statistics/list'."
]:
    """Get aggregated email statistics in a specified time span.

    Use this tool to obtain aggregated statistics for emails sent within a specific time period. It also provides a list of all emails sent during that period."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/statistics/list",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing-email", "transactional-email"]))
async def publish_hubspot_email(
    context: ToolContext,
    hubspot_email_id: Annotated[
        str, "The unique identifier for the HubSpot marketing email to publish."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/emails/{emailId}/publish'."
]:
    """Publish a HubSpot marketing email.

    Use this tool to publish a marketing email in HubSpot by specifying the email ID. This should be called when you need to make an email live as part of a marketing campaign."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/publish".format(  # noqa: UP032
            emailId=hubspot_email_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_ab_test_email_variation(
    context: ToolContext,
    email_content_id: Annotated[
        str, "ID of the email content to create a variation for A/B testing."
    ],
    variation_name: Annotated[
        str,
        "The name of the new email variation for the A/B test. Provide a descriptive name to easily identify this variation.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/emails/ab-test/create-variation'.",
]:
    """Create a new variation for an email A/B test.

    Use this tool to generate a new variation of an existing email for conducting A/B tests in your marketing campaigns. This is useful for testing different email content to optimize engagement and performance."""
    request_data: Any = {"variationName": variation_name, "contentId": email_content_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/ab-test/create-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def fetch_email_statistics(
    context: ToolContext,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/emails/statistics/histogram'.",
]:
    """Fetch aggregated email statistics in specified intervals.

    This tool retrieves aggregated email statistics over specified time intervals, providing insights into email performance during each period."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/statistics/histogram",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_email_ab_test_variation(
    context: ToolContext,
    email_identifier: Annotated[
        str, "The unique identifier of the marketing email to obtain its A/B test variation."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/emails/{emailId}/ab-test/get-variation'.",
]:
    """Retrieve the variation of an A/B test marketing email.

    Use this tool to get the opposite variation of a specified A/B marketing email. Ideal for determining the alternative version presented in a marketing email campaign."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/ab-test/get-variation".format(  # noqa: UP032
            emailId=email_identifier
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def reset_email_draft_to_live(
    context: ToolContext,
    email_id: Annotated[
        str,
        "The unique identifier for the email draft to be reset. This is a string value used to specify which draft to revert.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/emails/{emailId}/draft/reset'.",
]:
    """Reset an email draft to match the live version.

    Use this tool to reset an email draft to a copy of its currently live version in HubSpot Marketing. This is useful when you want to discard changes made to a draft and revert to the original published version."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/draft/reset".format(  # noqa: UP032
            emailId=email_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_email_revision_to_draft(
    context: ToolContext,
    email_identifier: Annotated[
        str,
        "The unique identifier of the marketing email whose revision is to be restored to draft. This should match the specific email ID format used by HubSpot.",
    ],
    revision_id: Annotated[
        str, "The unique identifier of the email revision to be restored to draft state."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/emails/{emailId}/revisions/{revisionId}/restore-to-draft'.",
]:
    """Restore a previous email revision to draft state.

    Use this tool to revert a marketing email back to a previous revision, setting it to DRAFT. This will overwrite any current content in the draft for that email."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/revisions/{revisionId}/restore-to-draft".format(  # noqa: UP032
            emailId=email_identifier, revisionId=revision_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_email_draft(
    context: ToolContext,
    email_id: Annotated[
        str,
        "The unique identifier of the email to retrieve the draft for. This ID is used to specify which email's draft or published version should be accessed.",
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/emails/{emailId}/draft'."
]:
    """Retrieve the draft version of a specified email.

    This tool retrieves the draft version of a specified email by its ID from HubSpot Marketing. If no draft exists, the published version is returned instead. It should be used when you need to access the current draft or latest version of an email."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/draft".format(emailId=email_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_email_draft(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    email_id: Annotated[
        str | None,
        "The unique identifier of the marketing email to update or create a draft for. This is required to specify which email draft you are modifying.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/marketing/v3/emails/{emailId}/draft'."
]:
    """Create or update the draft version of a marketing email.

    This tool creates or updates the draft version of a marketing email. If no draft exists, it creates a draft from the current live email and applies changes. Use this tool to modify email drafts before finalizing them.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEEMAILDRAFT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not email_id:
        missing_params.append(("email_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEEMAILDRAFT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEEMAILDRAFT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/draft".format(emailId=email_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEEMAILDRAFT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_email_revisions(
    context: ToolContext,
    email_id: Annotated[
        str, "The unique identifier of the marketing email to retrieve revisions for."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/emails/{emailId}/revisions'."
]:
    """Retrieve all versions of a marketing email.

    Use this tool to get a comprehensive list of all versions of a specified marketing email, each containing the full state of that version."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/revisions".format(  # noqa: UP032
            emailId=email_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing-email", "transactional-email"]))
async def unpublish_marketing_email(
    context: ToolContext,
    email_identifier: Annotated[
        str, "The unique identifier of the email you wish to unpublish in HubSpot Marketing."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/emails/{emailId}/unpublish'.",
]:
    """Unpublish a HubSpot marketing email.

    This tool is used to unpublish a specific email in HubSpot Marketing. It should be called when you want to deactivate or withdraw an email from being publicly available."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/unpublish".format(  # noqa: UP032
            emailId=email_identifier
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_marketing_email_revision(
    context: ToolContext,
    email_id: Annotated[
        str,
        "The unique identifier for the marketing email. This ID is required to specify which email's revision details to retrieve.",
    ],
    email_revision_id: Annotated[
        str,
        "The unique ID of the specific email revision to retrieve. Provide the revisionId for accessing the exact version.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/emails/{emailId}/revisions/{revisionId}'.",
]:
    """Retrieve a specific revision of a marketing email.

    Use this tool to get details about a specific revision of a marketing email by providing the email and revision IDs."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/revisions/{revisionId}".format(  # noqa: UP032
            emailId=email_id, revisionId=email_revision_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def clone_hubspot_email(
    context: ToolContext,
    original_email_id: Annotated[
        str, "The unique identifier of the email you wish to clone in HubSpot."
    ],
    cloned_email_name: Annotated[
        str | None,
        "The new name for the cloned email. This should be a descriptive string to identify the copy.",
    ] = None,
    email_language: Annotated[
        str | None,
        "Specify the language for the cloned email, e.g., 'en' for English, 'fr' for French.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/emails/clone'."]:
    """Clone an existing HubSpot marketing email.

    Use this tool to create a copy of an existing email in HubSpot's marketing platform. Ideal for reusing email templates and maintaining consistency in marketing campaigns."""
    request_data: Any = {
        "id": original_email_id,
        "cloneName": cloned_email_name,
        "language": email_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/clone",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def hubspot_marketing_email_filter(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/emails/'."]:
    """Retrieve and filter HubSpot marketing emails.

    Use this tool to retrieve and filter marketing emails from HubSpot. It allows you to apply various filters to find specific sets of emails based on your criteria."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_marketing_email(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'post-/marketing/v3/emails/'."]:
    """Create a new marketing email in HubSpot.

    Use this tool to create a new marketing email through HubSpot's marketing platform, allowing for effective email campaigns.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMARKETINGEMAIL"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGEMAIL"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGEMAIL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/emails/",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMARKETINGEMAIL"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_email_revision(
    context: ToolContext,
    email_id: Annotated[
        str,
        "The unique identifier for the marketing email you want to restore. This ID is required to specify which email's revision needs restoration.",
    ],
    revision_id: Annotated[
        str, "The unique identifier for the specific revision of the email to be restored."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/emails/{emailId}/revisions/{revisionId}/restore'.",
]:
    """Restore a previous revision of a marketing email.

    Use this tool to revert a marketing email to a specified previous revision. The current version is archived, and the restored revision is assigned a new version number."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}/revisions/{revisionId}/restore".format(  # noqa: UP032
            emailId=email_id, revisionId=revision_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_marketing_email_details(
    context: ToolContext,
    email_id: Annotated[
        str,
        "The unique identifier for the specific marketing email. Required to retrieve email details.",
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/emails/{emailId}'."
]:
    """Retrieve details for a specific marketing email.

    Use this tool to get comprehensive information about a marketing email by specifying its email ID."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}".format(emailId=email_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_marketing_email(
    context: ToolContext,
    email_id: Annotated[str, "The unique ID of the marketing email to be deleted."],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/marketing/v3/emails/{emailId}'."
]:
    """Delete an existing marketing email by ID.

    Use this tool to remove a specific marketing email from the HubSpot system by providing its unique email ID. This is useful for managing email lists and ensuring outdated or incorrect emails are removed."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}".format(emailId=email_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_marketing_email_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    email_identifier: Annotated[
        str | None,
        "The unique identifier of the marketing email to be updated. Required to specify which email's properties are being changed.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/marketing/v3/emails/{emailId}'."
]:
    """Change properties of a marketing email.

    Use this tool to update properties of an existing marketing email by specifying the email ID. It helps in modifying content or settings of your marketing campaigns.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEMAILPROPERTIES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not email_identifier:
        missing_params.append(("email_identifier", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEMAILPROPERTIES"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEMAILPROPERTIES"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/emails/{emailId}".format(emailId=email_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEMAILPROPERTIES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_marketing_event_participations_breakdown(
    context: ToolContext,
    marketing_event_id: Annotated[int, "The internal id of the marketing event in HubSpot."],
    contact_identifier: Annotated[
        str | None, "The identifier of the Contact. It can be an email or internal ID."
    ] = None,
    last_retrieved_position_cursor: Annotated[
        str | None, "The cursor indicating the position of the last retrieved item for pagination."
    ] = None,
    participation_state: Annotated[
        str | None,
        "The participation state filter, which can be REGISTERED, CANCELLED, ATTENDED, or NO_SHOW.",
    ] = None,
    response_size_limit: Annotated[
        int | None, "Set the maximum number of records to return. Default is 10, maximum is 100."
    ] = 10,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/participations/{marketingEventId}/breakdown_getParticipationsBreakdownByMarketingEventId'.",
]:
    """Retrieve participations breakdown for a specific marketing event.

    This tool fetches a detailed breakdown of participations for a specific marketing event identified by marketingEventId. It can be used to analyze participation metrics and insights for a given event."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/participations/{marketingEventId}/breakdown".format(  # noqa: UP032
            marketingEventId=marketing_event_id
        ),
        method="GET",
        params=remove_none_values({
            "contactIdentifier": contact_identifier,
            "state": participation_state,
            "limit": response_size_limit,
            "after": last_retrieved_position_cursor,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def record_marketing_event_attendance(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_event_id: Annotated[
        str | None,
        "The identifier for the marketing event in the external event application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    new_subscriber_state: Annotated[
        str | None,
        "Specifies the new state of the contact in relation to the marketing event, such as 'register', 'attend', or 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    external_account_id: Annotated[
        str | None,
        "The ID identifying the account associated with the marketing event in the external application.  Only used when mode is 'execute'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/attendance/{externalEventId}/{subscriberState}/create_recordByContactIds'.",
]:
    """Record participation of HubSpot contacts in a Marketing Event.

    Use this tool to mark multiple HubSpot contacts as having participated in a specific marketing event. It is useful for updating contact records and timelines when contacts attend an event. The tool supports additional properties like 'joinedAt' and 'leftAt' for the 'attend' state.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["RECORDMARKETINGEVENTATTENDANCE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_event_id:
        missing_params.append(("external_event_id", "path"))
    if not new_subscriber_state:
        missing_params.append(("new_subscriber_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDMARKETINGEVENTATTENDANCE"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDMARKETINGEVENTATTENDANCE"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/attendance/{externalEventId}/{subscriberState}/create".format(  # noqa: UP032
            externalEventId=external_event_id, subscriberState=new_subscriber_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RECORDMARKETINGEVENTATTENDANCE"],
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def record_hubspot_subscriber_state(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_account_id: Annotated[
        str | None,
        "The account ID associated with this marketing event in the external event application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    event_id_in_external_app: Annotated[
        str | None,
        "The ID of the marketing event in the external application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    subscriber_state: Annotated[
        str | None,
        "The new subscriber state for the HubSpot contacts and the specified marketing event, such as 'register', 'attend', or 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/{externalEventId}/{subscriberState}/upsert_upsertByContactId'.",
]:
    """Record subscriber state for HubSpot contacts and events.

    Record a subscriber state between multiple HubSpot contacts and a marketing event using HubSpot contact IDs. Note: Contacts must already exist in HubSpot.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["RECORDHUBSPOTSUBSCRIBERSTATE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_account_id:
        missing_params.append(("external_account_id", "query"))
    if not event_id_in_external_app:
        missing_params.append(("event_id_in_external_app", "path"))
    if not subscriber_state:
        missing_params.append(("subscriber_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDHUBSPOTSUBSCRIBERSTATE"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDHUBSPOTSUBSCRIBERSTATE"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}/{subscriberState}/upsert".format(  # noqa: UP032
            externalEventId=event_id_in_external_app, subscriberState=subscriber_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RECORDHUBSPOTSUBSCRIBERSTATE"],
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_marketing_event_details(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The accountId associated with this marketing event in the external event application."
    ],
    marketing_event_id: Annotated[
        str, "The unique identifier of the marketing event in the external application."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/events/{externalEventId}_getDetails'.",
]:
    """Retrieve details of a specific marketing event.

    Fetches details of a marketing event using its externalEventId. Only events created by the requesting app can be accessed."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}".format(  # noqa: UP032
            externalEventId=marketing_event_id
        ),
        method="GET",
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def upsert_marketing_event(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_event_id: Annotated[
        str | None,
        "The ID of the marketing event in the external application. Used to upsert the event in HubSpot.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/marketing-events/events/{externalEventId}_upsert'.",
]:
    """Upsert a marketing event in HubSpot.

    Upserts a marketing event using the provided external event ID. If an event with that ID exists, it updates the event; otherwise, it creates a new one.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_event_id:
        missing_params.append(("external_event_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}".format(  # noqa: UP032
            externalEventId=external_event_id
        ),
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def delete_marketing_event(
    context: ToolContext,
    external_account_id: Annotated[
        str,
        "The account ID associated with the marketing event to be deleted in the external event application.",
    ],
    marketing_event_id: Annotated[
        str, "The ID of the marketing event in the external event application to be deleted."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/marketing-events/events/{externalEventId}_archive'.",
]:
    """Deletes a specified HubSpot marketing event.

    Use this tool to delete an existing marketing event in HubSpot by specifying the externalEventId. The event must have been created by the same app requesting the deletion."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}".format(  # noqa: UP032
            externalEventId=marketing_event_id
        ),
        method="DELETE",
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def update_marketing_event_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_account_id: Annotated[
        str | None,
        "The account ID associated with this marketing event in the external event application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    external_event_id: Annotated[
        str | None,
        "The unique identifier of the marketing event in the external event application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/marketing/v3/marketing-events/events/{externalEventId}_update'.",
]:
    """Update details of an existing marketing event.

    Use this tool to update the details of an existing marketing event identified by its external event ID. It is applicable only for events created by the same app.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTDETAILS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_account_id:
        missing_params.append(("external_account_id", "query"))
    if not external_event_id:
        missing_params.append(("external_event_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTDETAILS"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTDETAILS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}".format(  # noqa: UP032
            externalEventId=external_event_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTDETAILS"],
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def upsert_marketing_events(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/upsert_upsert'.",
]:
    """Upsert multiple marketing events in HubSpot.

    Upserts multiple marketing events. Updates existing events with the same ID or creates new ones if they don't exist. Only events created by the same app can be updated.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENTS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTMARKETINGEVENTS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def record_event_participation_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_event_id: Annotated[
        str | None,
        "The ID of the marketing event in the external event application.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    subscriber_state: Annotated[
        str | None,
        "The new subscriber state for HubSpot contacts in the specified event. Options: 'register', 'attend', 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    external_account_id: Annotated[
        str | None,
        "The account ID linked to the marketing event in the external event application.  Only used when mode is 'execute'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/attendance/{externalEventId}/{subscriberState}/email-create_recordByContactEmails'.",
]:
    """Record participation of contacts in a HubSpot marketing event.

    Use this tool to record the attendance of HubSpot contacts in a marketing event using their email addresses. It can automatically create contacts if they do not exist and adds a timeline event for them. This is particularly useful for logging who attended or participated in specific marketing events.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["RECORDEVENTPARTICIPATIONHUBSPOT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_event_id:
        missing_params.append(("external_event_id", "path"))
    if not subscriber_state:
        missing_params.append(("subscriber_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDEVENTPARTICIPATIONHUBSPOT"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["RECORDEVENTPARTICIPATIONHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/attendance/{externalEventId}/{subscriberState}/email-create".format(  # noqa: UP032
            externalEventId=external_event_id, subscriberState=subscriber_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RECORDEVENTPARTICIPATIONHUBSPOT"],
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_marketing_event_participation_breakdown(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The ID associated with the marketing event's account in the external application."
    ],
    external_event_id: Annotated[
        str, "The ID of the marketing event in the external event application."
    ],
    contact_identifier: Annotated[
        str | None, "The unique identifier for the contact, such as an email or internal ID."
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "The cursor indicating the position of the last retrieved item, used for pagination.",
    ] = None,
    participation_state_filter: Annotated[
        str | None,
        "The participation state to filter results. Possible values: REGISTERED, CANCELLED, ATTENDED, NO_SHOW.",
    ] = None,
    response_size_limit: Annotated[
        int | None, "Maximum number of participations to retrieve. Default is 10, maximum is 100."
    ] = 10,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/participations/{externalAccountId}/{externalEventId}/breakdown_getParticipationsBreakdownByExternalEventId'.",
]:
    """Retrieve the participation breakdown for a marketing event.

    This tool fetches the participation breakdown for a specified marketing event, filtered by externalAccountId and externalEventId. It provides detailed insights on event participation metrics."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/participations/{externalAccountId}/{externalEventId}/breakdown".format(  # noqa: UP032
            externalAccountId=external_account_id, externalEventId=external_event_id
        ),
        method="GET",
        params=remove_none_values({
            "contactIdentifier": contact_identifier,
            "state": participation_state_filter,
            "limit": response_size_limit,
            "after": pagination_cursor,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def fetch_event_details(
    context: ToolContext,
    marketing_event_object_id: Annotated[
        str,
        "The internal ID of the marketing event in HubSpot. Used to fetch specific event details.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/{objectId}_getByObjectId'.",
]:
    """Fetch details of a marketing event by its object ID.

    Use this tool to retrieve detailed information about a specific marketing event by providing its object ID. This can be useful for gaining insights into the event's specifics and managing marketing activities."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{objectId}".format(  # noqa: UP032
            objectId=marketing_event_object_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def remove_marketing_event(
    context: ToolContext,
    marketing_event_id: Annotated[
        str, "The internal ID of the marketing event to be deleted in HubSpot."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/marketing-events/{objectId}_archiveByObjectId'.",
]:
    """Deletes a specified marketing event by objectId.

    Use this tool to delete an existing marketing event in HubSpot Marketing using its objectId. Call this tool when you need to remove a marketing event from the system."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{objectId}".format(  # noqa: UP032
            objectId=marketing_event_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def modify_event_information(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    event_internal_id: Annotated[
        str | None,
        "The internal ID of the marketing event in HubSpot to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/marketing/v3/marketing-events/{objectId}_updateByObjectId'.",
]:
    """Update details of a specific marketing event.

    Use this tool to update the details of an existing marketing event by providing the object's ID. It confirms whether the update was successful.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["MODIFYEVENTINFORMATION"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not event_internal_id:
        missing_params.append(("event_internal_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["MODIFYEVENTINFORMATION"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["MODIFYEVENTINFORMATION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{objectId}".format(  # noqa: UP032
            objectId=event_internal_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["MODIFYEVENTINFORMATION"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def get_marketing_event_associated_lists(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The account ID associated with the marketing event in the external application."
    ],
    external_event_id: Annotated[
        str, "The ID of the marketing event from the external event application."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists_getAllByExternalAccountAndEventIds'.",
]:
    """Retrieve lists associated with a specific marketing event.

    Call this tool to get lists associated with a marketing event using external account and event IDs. Useful for accessing participant or attendee lists linked to specific marketing events."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists".format(  # noqa: UP032
            externalAccountId=external_account_id, externalEventId=external_event_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def get_marketing_event_lists(
    context: ToolContext,
    marketing_event_id: Annotated[
        str, "The internal ID of the marketing event in HubSpot to retrieve associated lists."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/associations/{marketingEventId}/lists_getAllByMarketingEventId'.",
]:
    """Retrieve lists associated with a specific marketing event.

    This tool fetches all lists associated with a given marketing event using the marketing event ID. It should be called when you need to obtain lists connected to a particular marketing event for analysis or reporting purposes."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{marketingEventId}/lists".format(  # noqa: UP032
            marketingEventId=marketing_event_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def register_event_participation(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    marketing_event_id: Annotated[
        str | None,
        "The internal ID of the marketing event in HubSpot. It is required to log participation for contacts.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    attendance_state: Annotated[
        str | None,
        "The attendance state for the contact in the event. Options: 'register', 'attend', or 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/{objectId}/attendance/{subscriberState}/email-create_recordByEmail'.",
]:
    """Logs event participation for contacts using email addresses.

    Records participation of HubSpot contacts in a marketing event using emails. Automatically creates non-existing contacts and logs the event on their timeline. Applicable for 'attend' state, with optional 'joinedAt' and 'leftAt' properties.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["REGISTEREVENTPARTICIPATION"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not marketing_event_id:
        missing_params.append(("marketing_event_id", "path"))
    if not attendance_state:
        missing_params.append(("attendance_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["REGISTEREVENTPARTICIPATION"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["REGISTEREVENTPARTICIPATION"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{objectId}/attendance/{subscriberState}/email-create".format(  # noqa: UP032
            objectId=marketing_event_id, subscriberState=attendance_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REGISTEREVENTPARTICIPATION"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_all_marketing_events(
    context: ToolContext,
    cursor_position_after: Annotated[
        str | None,
        "The cursor indicating the position of the last retrieved item for pagination purposes.",
    ] = None,
    response_limit: Annotated[
        int | None, "Sets the maximum number of marketing events to retrieve, between 10 and 100."
    ] = 10,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/marketing/v3/marketing-events/_getAll'."
]:
    """Retrieve all marketing events from the portal.

    This tool fetches all available marketing events, including manually created ones and those generated through the application. The events are sorted by objectId."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/",
        method="GET",
        params=remove_none_values({"after": cursor_position_after, "limit": response_limit}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def record_event_attendance(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    marketing_event_id: Annotated[
        str | None,
        "The internal ID of the marketing event in HubSpot for which attendance is being recorded.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    attendance_state: Annotated[
        str | None,
        "Specifies the attendance state of the contact: 'register', 'attend', or 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/{objectId}/attendance/{subscriberState}/create_recordByContactId'.",
]:
    """Record participation of HubSpot contacts in a marketing event.

    Use this tool to record the attendance of multiple HubSpot contacts at a marketing event using their HubSpot contact IDs. This also adds a timeline event to each contact's record. The tool is applicable for the \"attend\" state with optional properties like \"joinedAt\" and \"leftAt\".

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["RECORDEVENTATTENDANCE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not marketing_event_id:
        missing_params.append(("marketing_event_id", "path"))
    if not attendance_state:
        missing_params.append(("attendance_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["RECORDEVENTATTENDANCE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["RECORDEVENTATTENDANCE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{objectId}/attendance/{subscriberState}/create".format(  # noqa: UP032
            objectId=marketing_event_id, subscriberState=attendance_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RECORDEVENTATTENDANCE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def update_marketing_events_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/batch/update_updateByObjectId'.",
]:
    """Update multiple marketing events by objectId.

    This tool updates multiple marketing events in the HubSpot portal based on their objectId, if they exist. It should be called when you need to batch update event details efficiently.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTSBATCH"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMARKETINGEVENTSBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def delete_marketing_events(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/batch/archive_archiveByObjectId'.",
]:
    """Delete multiple marketing events by object ID.

    This tool deletes specified marketing events based on their object IDs. Use it to clean up events from the portal. It returns a status indicating whether all or only some events were successfully deleted.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["DELETEMARKETINGEVENTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["DELETEMARKETINGEVENTS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["DELETEMARKETINGEVENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["DELETEMARKETINGEVENTS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_event_participation_counters(
    context: ToolContext,
    event_id: Annotated[
        str,
        "The unique identifier for the marketing event in the external application. Required to retrieve participation data.",
    ],
    external_account_id: Annotated[
        str, "The account ID associated with the marketing event in the external application."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/participations/{externalAccountId}/{externalEventId}_getParticipationsCountersByEventExternalId'.",
]:
    """Retrieve participation counters for a specific marketing event.

    This tool fetches participation metrics for a marketing event using the externalAccountId and externalEventId. It should be called when you need detailed participation statistics for a specific event in HubSpot's marketing platform."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/participations/{externalAccountId}/{externalEventId}".format(  # noqa: UP032
            externalAccountId=external_account_id, externalEventId=event_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def search_marketing_events_by_external_id(
    context: ToolContext,
    external_event_id: Annotated[
        str,
        "The ID of the marketing event in the external event application used to search for matching events.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/{externalEventId}/identifiers_searchPortalEvents'.",
]:
    """Search for marketing events by external ID.

    This tool retrieves all marketing events whose externalEventId matches the provided value. It returns object IDs and additional details for each matching event. Useful for finding all marketing events linked to a specific external ID after creation."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/{externalEventId}/identifiers".format(  # noqa: UP032
            externalEventId=external_event_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_marketing_event_participation_counters(
    context: ToolContext,
    marketing_event_id: Annotated[
        int,
        "The internal ID of the marketing event in HubSpot, required to fetch participation counters.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/participations/{marketingEventId}_getParticipationsCountersByMarketingEventId'.",
]:
    """Retrieve participation counters for a marketing event.

    This tool fetches the participation statistics for a specified marketing event using its internal identifier."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/participations/{marketingEventId}".format(  # noqa: UP032
            marketingEventId=marketing_event_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def remove_marketing_events(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/delete_archive'.",
]:
    """Delete multiple HubSpot marketing events by specific IDs.

    This tool deletes multiple HubSpot marketing events based on provided external account, event, and app IDs. It is used to remove events created by the calling app, ensuring events from other apps remain unaffected.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["REMOVEMARKETINGEVENTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REMOVEMARKETINGEVENTS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REMOVEMARKETINGEVENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/delete",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REMOVEMARKETINGEVENTS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def cancel_marketing_event(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The account ID associated with the marketing event in the external event application."
    ],
    external_event_id: Annotated[
        str,
        "The ID of the marketing event in the external event application. Required to identify the event to be cancelled.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/{externalEventId}/cancel_cancel'.",
]:
    """Cancel a HubSpot marketing event.

    Use this tool to mark a specific HubSpot marketing event as cancelled by providing the external event ID."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}/cancel".format(  # noqa: UP032
            externalEventId=external_event_id
        ),
        method="POST",
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def associate_list_with_marketing_event(
    context: ToolContext,
    list_id: Annotated[str, "The ILS ID of the list to associate with the marketing event."],
    marketing_event_id: Annotated[
        str, "The internal ID of the marketing event in HubSpot for association."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/marketing-events/associations/{marketingEventId}/lists/{listId}_associateByMarketingEventId'.",
]:
    """Associate a list with a marketing event by their IDs.

    This tool associates a specific list with a marketing event using the marketing event ID and the list ID. It is used when you need to connect a list of contacts to a marketing event for tracking and management purposes."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{marketingEventId}/lists/{listId}".format(  # noqa: UP032
            marketingEventId=marketing_event_id, listId=list_id
        ),
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def disassociate_list_from_marketing_event(
    context: ToolContext,
    list_identifier: Annotated[
        str, "The ILS ID of the list to be disassociated from the marketing event."
    ],
    marketing_event_id: Annotated[
        str, "The internal ID of the marketing event in HubSpot to disassociate the list from."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/marketing-events/associations/{marketingEventId}/lists/{listId}_disassociateByMarketingEventId'.",
]:
    """Disassociate a list from a marketing event using event and list IDs.

    Use this tool to remove the association between a specific list and a marketing event by providing the marketing event ID and the list ID. It ensures that the list is no longer linked to the specified marketing event."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{marketingEventId}/lists/{listId}".format(  # noqa: UP032
            marketingEventId=marketing_event_id, listId=list_identifier
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def record_subscriber_state(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    external_account_id: Annotated[
        str | None,
        "The account ID linked to the marketing event in the external application, required for identifying events.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    marketing_event_id: Annotated[
        str | None,
        "The unique identifier for the marketing event in the external application. Required to record subscriber state.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    subscriber_state: Annotated[
        str | None,
        "The new subscriber state for a HubSpot contact in the specified marketing event. Options include 'register', 'attend', or 'cancel'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/{externalEventId}/{subscriberState}/email-upsert_upsertByContactEmail'.",
]:
    """Record a subscriber's state for a marketing event using email.

    This tool records the subscriber state of a HubSpot contact in relation to a specific marketing event using the contact's email. Note: the contact must already exist in HubSpot.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["RECORDSUBSCRIBERSTATE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not external_account_id:
        missing_params.append(("external_account_id", "query"))
    if not marketing_event_id:
        missing_params.append(("marketing_event_id", "path"))
    if not subscriber_state:
        missing_params.append(("subscriber_state", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["RECORDSUBSCRIBERSTATE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["RECORDSUBSCRIBERSTATE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}/{subscriberState}/email-upsert".format(  # noqa: UP032
            externalEventId=marketing_event_id, subscriberState=subscriber_state
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RECORDSUBSCRIBERSTATE"],
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def link_list_to_event(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The account ID associated with the marketing event in the external event application."
    ],
    external_event_id: Annotated[
        str,
        "The ID of the marketing event in the external event application. Used to specify which event to associate with the list.",
    ],
    ils_list_id: Annotated[str, "The ILS ID of the list to associate with the marketing event."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists/{listId}_associateByExternalAccountAndEventIds'.",
]:
    """Associates a marketing list with an event in HubSpot.

    This tool associates a list with a marketing event using the external account ID, external event ID, and ILS list ID. It should be called when you need to link a specific list to a marketing event for tracking or organizational purposes."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists/{listId}".format(  # noqa: UP032
            externalAccountId=external_account_id,
            externalEventId=external_event_id,
            listId=ils_list_id,
        ),
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def disassociate_marketing_event_list(
    context: ToolContext,
    external_account_id: Annotated[
        str, "The account ID linked to the marketing event in the external application."
    ],
    list_id: Annotated[str, "The ILS ID of the list to be disassociated from the marketing event."],
    marketing_event_id: Annotated[
        str, "ID of the marketing event in the external application to be disassociated."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists/{listId}_disassociateByExternalAccountAndEventIds'.",
]:
    """Disassociate a list from a HubSpot marketing event using IDs.

    Use this tool to disassociate a list from a specific marketing event in HubSpot by providing the external account ID, external event ID, and list ID. This action is useful when you need to update or manage your marketing event associations."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/associations/{externalAccountId}/{externalEventId}/lists/{listId}".format(  # noqa: UP032
            externalAccountId=external_account_id,
            externalEventId=marketing_event_id,
            listId=list_id,
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def mark_marketing_event_completed(
    context: ToolContext,
    event_end_datetime: Annotated[
        str, "The date and time when the marketing event ended. Expected in ISO 8601 format."
    ],
    event_start_date_time: Annotated[
        str, "The start date and time of the marketing event, formatted as an ISO 8601 string."
    ],
    external_account_id: Annotated[
        str, "The account ID associated with the marketing event in the external application."
    ],
    marketing_event_id: Annotated[
        str, "The unique ID of the marketing event in the external event application."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events/{externalEventId}/complete_complete'.",
]:
    """Mark a marketing event as completed in HubSpot.

    Use this tool to mark a marketing event as completed in HubSpot, identified by its externalEventId. This confirms that the specified event has been finalized."""
    request_data: Any = {"startDateTime": event_start_date_time, "endDateTime": event_end_datetime}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/{externalEventId}/complete".format(  # noqa: UP032
            externalEventId=marketing_event_id
        ),
        method="POST",
        params=remove_none_values({"externalAccountId": external_account_id}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.write"]))
async def create_marketing_event(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/marketing-events/events_create'.",
]:
    """Create a new marketing event in HubSpot.

    This tool is used to create a new marketing event within the HubSpot platform. It should be called when a new event needs to be added to HubSpot's marketing event system.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMARKETINGEVENT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGEVENT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMARKETINGEVENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMARKETINGEVENT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def get_contact_participations_breakdown(
    context: ToolContext,
    contact_identifier: Annotated[
        str, "The identifier of the contact, which can be either an email or an internal ID."
    ],
    pagination_cursor: Annotated[
        str | None, "Cursor for the last retrieved item to manage pagination."
    ] = None,
    participation_state: Annotated[
        str | None,
        "The participation state for the contact. Options: REGISTERED, CANCELLED, ATTENDED, NO_SHOW.",
    ] = None,
    response_size_limit: Annotated[
        int | None,
        "Specify the maximum number of participation records to return. The default is 10, and the maximum is 100.",
    ] = 10,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/participations/contacts/{contactIdentifier}/breakdown_getParticipationsBreakdownByContactId'.",
]:
    """Retrieve a contact's event participation details by ID or email.

    This tool retrieves participation details for a specific contact in marketing events using their email or internal ID. It is useful for analyzing a contact's engagement in various events."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/participations/contacts/{contactIdentifier}/breakdown".format(  # noqa: UP032
            contactIdentifier=contact_identifier
        ),
        method="GET",
        params=remove_none_values({
            "state": participation_state,
            "limit": response_size_limit,
            "after": pagination_cursor,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.objects.marketing_events.read"]))
async def find_hubspot_marketing_events(
    context: ToolContext,
    external_event_id: Annotated[
        str,
        "The ID of the marketing event in the external application (externalEventId) to search for.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/marketing-events/events/search_doSearch'.",
]:
    """Fetch HubSpot marketing events by externalEventId.

    Use this tool to retrieve marketing events from HubSpot where the externalEventId matches the given request parameter. It only returns events created by the app making the request, excluding those made by other apps."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/marketing-events/events/search",
        method="GET",
        params=remove_none_values({"q": external_event_id}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["marketing-email", "transactional-email"]))
async def send_marketing_email(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/marketing/v4/email/single-send'."
]:
    """Send a template email to a specific recipient.

    This tool sends a marketing email using a predefined template to a specified recipient via HubSpot. It's useful for automating email campaigns and reaching target audiences efficiently.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["SENDMARKETINGEMAIL"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["SENDMARKETINGEMAIL"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["SENDMARKETINGEMAIL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v4/email/single-send",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SENDMARKETINGEMAIL"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def send_transactional_email(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/transactional/single-email/send_sendEmail'.",
]:
    """Send a transactional email asynchronously.

    Use this tool to send a transactional email asynchronously and obtain the status of the email send with a statusId, which can be queried further using the Email Send Status API.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["SENDTRANSACTIONALEMAIL"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["SENDTRANSACTIONALEMAIL"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["SENDTRANSACTIONALEMAIL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/marketing/v3/transactional/single-email/send",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SENDTRANSACTIONALEMAIL"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def query_smtp_tokens(
    context: ToolContext,
    campaign_name: Annotated[
        str | None, "Name of the campaign tied to the SMTP API token to query tokens."
    ] = None,
    email_campaign_id: Annotated[
        str | None,
        "Identifier assigned to the campaign provided during the token creation. Used to retrieve a specific SMTP API token.",
    ] = None,
    maximum_tokens_to_return: Annotated[
        int | None, "Specify the maximum number of SMTP API tokens to return in the response."
    ] = None,
    result_pagination_start: Annotated[
        str | None,
        "Specify a starting point for retrieving the next set of results. Use this for paginating through results.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/transactional/smtp-tokens_getTokensPage'.",
]:
    """Retrieve SMTP API tokens by campaign name or emailCampaignId.

    Use this tool to query multiple SMTP API tokens associated with a campaign by its name, or to retrieve a single token using an emailCampaignId. This is useful for managing email campaigns and accessing specific token information."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/transactional/smtp-tokens",
        method="GET",
        params=remove_none_values({
            "campaignName": campaign_name,
            "emailCampaignId": email_campaign_id,
            "after": result_pagination_start,
            "limit": maximum_tokens_to_return,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def create_smtp_api_token(
    context: ToolContext,
    campaign_name: Annotated[str, "The name of the campaign associated with the SMTP API token."],
    create_contact_for_recipients: Annotated[
        bool,
        "Indicates whether a contact should be created for email recipients. Set to true to create a contact.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/transactional/smtp-tokens_createToken'.",
]:
    """Creates a SMTP API token for transaction emails.

    This tool is used to create a new SMTP API token for sending transactional emails through HubSpot's marketing service. It should be called when a new token is needed for email operations."""
    request_data: Any = {
        "createContact": create_contact_for_recipients,
        "campaignName": campaign_name,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/transactional/smtp-tokens",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def reset_password_for_token(
    context: ToolContext,
    token_identifier: Annotated[
        str, "The unique string identifier for the token, used to reset its password."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/marketing/v3/transactional/smtp-tokens/{tokenId}/password-reset_resetPassword'.",
]:
    """Resets the password for a specified token.

    This tool allows you to create a new password for a specified token, invalidating the old password. Use it when you need to reset the password associated with a specific SMTP token in HubSpot's marketing system."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/transactional/smtp-tokens/{tokenId}/password-reset".format(  # noqa: UP032
            tokenId=token_identifier
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def get_smtp_token_by_id(
    context: ToolContext,
    smtp_token_id: Annotated[str, "The unique identifier of the SMTP token to be queried."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/marketing/v3/transactional/smtp-tokens/{tokenId}_getTokenById'.",
]:
    """Retrieve details of an SMTP token using its ID.

    This tool is used to query a specific SMTP token by its ID within the HubSpot Marketing platform. Call this tool when you need to access detailed information about a particular SMTP transactional token."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/transactional/smtp-tokens/{tokenId}".format(  # noqa: UP032
            tokenId=smtp_token_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["transactional-email"]))
async def delete_smtp_token(
    context: ToolContext,
    smtp_token_id: Annotated[
        str,
        "The unique identifier of the SMTP token to be deleted, provided during token creation.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/marketing/v3/transactional/smtp-tokens/{tokenId}_archiveToken'.",
]:
    """Delete an SMTP token by ID in HubSpot Marketing.

    Use this tool to delete a specific SMTP token from HubSpot Marketing by providing the token ID. This is useful for managing and revoking access tokens when they are no longer needed or for security reasons."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/marketing/v3/transactional/smtp-tokens/{tokenId}".format(  # noqa: UP032
            tokenId=smtp_token_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}
