"""Arcade Starter Tools for Miro

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")  # noqa: TRY003


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-miro"))
async def get_access_token_info(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'token-info'."]:
    """Retrieve detailed information about an access token.

    Use this tool to get information about an OAuth access token, including its type, scopes, associated team, user details, creation date and time, and the creator's identity."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v1/oauth-token",
        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-miro"))
async def get_recent_audit_logs(
    context: ToolContext,
    end_date_for_audit_logs: Annotated[
        str,
        "Retrieve audit logs created before this date and time. Format: UTC ISO 8601, including milliseconds and trailing Z.",  # noqa: E501
    ],
    start_date_time_for_audit_logs: Annotated[
        str,
        "Retrieve audit logs created after the specified UTC start date and time in ISO 8601 format, including milliseconds and 'Z'.",  # noqa: E501
    ],
    maximum_results_limit: Annotated[
        int | None,
        "Specify the maximum number of audit logs to retrieve in a single request. Defaults to 100 if not specified. Use a smaller number to limit the results or manage pagination efficiently.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "Cursor value for paginating through audit log results. Use the value returned in the previous response to obtain the next set of results.",  # noqa: E501
    ] = None,
    sort_order: Annotated[
        str | None,
        "Specifies the sort order for viewing audit logs: 'ASC' for ascending or 'DESC' for descending.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-audit-logs'."]:
    """Retrieve recent audit logs from the last 90 days.

    This tool retrieves a page of audit events from Miro for the last 90 days, useful for monitoring and reviewing recent activities. For older data, refer to the CSV export feature."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/audit/logs",
        method="GET",
        params=remove_none_values({
            "createdAfter": start_date_time_for_audit_logs,
            "createdBefore": end_date_for_audit_logs,
            "cursor": pagination_cursor,
            "limit": maximum_results_limit,
            "sorting": sort_order,
        }),
        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-miro"))
async def get_board_classification_settings(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique string ID of the organization for which to retrieve board classification settings.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'enterprise-dataclassification-organization-settings-get'.",
]:
    """Retrieve board classification settings for an organization.

    Use this tool to get the board classification settings of an existing organization within the Miro Enterprise plan. This is applicable only for Company Admins. Ensure you have the required 'organizations:read' scope and be aware of rate limits."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/data-classification-settings".format(  # noqa: UP032
            org_id=organization_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-miro"))
async def update_board_classification(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique identifier for the organization. Required for specifying which organization's team boards will be classified.",  # noqa: E501
    ],
    team_identifier: Annotated[
        str,
        "The unique identifier for the team whose board classification is being updated. This is used to specify which team's boards will be affected by the API call.",  # noqa: E501
    ],
    assign_to_not_classified_only: Annotated[
        bool | None,
        "If true, assign data classification only to non-classified boards; otherwise, assign to all boards.",  # noqa: E501
    ] = None,
    data_classification_label_id: Annotated[
        int | None, "The ID of the data classification label to assign to a team's boards."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'enterprise-dataclassification-team-boards-bulk'.",
]:
    """Update board classification for team boards in Miro.

    This tool updates the classification for either non-classified or all boards within a specified team in an Enterprise Miro account. It requires the user to have Company Admin privileges and the required scope of 'boards:write'. Ideal for Enterprise plan users needing to organize board classifications efficiently."""  # noqa: E501
    request_data = remove_none_values({
        "labelId": data_classification_label_id,
        "notClassifiedOnly": assign_to_not_classified_only,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/data-classification".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def get_team_board_classification_settings(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the organization. Required for retrieving team settings."
    ],
    team_identifier: Annotated[
        str,
        "ID of the team whose board classification settings you want to retrieve. Must be a string.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'enterprise-dataclassification-team-settings-get'.",
]:
    """Retrieve board classification settings for an enterprise team.

    Use this tool to get the board classification settings of an existing team within an enterprise organization. Ideal for enterprise plan users with Company Admin roles wishing to check data classification details."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/data-classification-settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_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-miro"))
async def update_team_board_classification_settings(
    context: ToolContext,
    organization_id: Annotated[
        str, "ID of the organization whose team's board classification settings will be updated."
    ],
    team_identifier: Annotated[
        str, "The unique identifier for the team to update board classification settings for."
    ],
    data_classification_default_label_id: Annotated[
        int | None,
        "The ID of the default data classification label to set for the team. This should be an integer.",  # noqa: E501
    ] = None,
    enable_data_classification: Annotated[
        bool | None,
        "Enable data classification for the team. Set to `true` to enable, `false` to disable.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'enterprise-dataclassification-team-settings-set'.",
]:
    """Updates board classification settings for a team's existing board.

    This tool updates the classification settings of a board within a specific team in an enterprise plan. It requires Company Admin permissions and is applicable only for enterprise users. Use this tool to modify board classification settings as necessary for compliance or organizational policy changes."""  # noqa: E501
    request_data = remove_none_values({
        "defaultLabelId": data_classification_default_label_id,
        "enabled": enable_data_classification,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/data-classification-settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def retrieve_board_classification(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier for the Miro board to retrieve classification."
    ],
    organization_id: Annotated[
        str,
        "The unique identifier of the organization for which you want to retrieve the board classification.",  # noqa: E501
    ],
    team_id: Annotated[
        str, "The unique identifier of the team to fetch the board classification from."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-dataclassification-board-get'."
]:
    """Get the data classification of a Miro board.

    Use this tool to retrieve the classification of a board within Miro. This is available for Enterprise plan users with Company Admin roles. It requires the 'boards:read' scope and is subject to Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/boards/{board_id}/data-classification".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, board_id=board_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-miro"))
async def set_board_classification(
    context: ToolContext,
    board_identifier: Annotated[
        str, "The unique identifier of the board to update its classification."
    ],
    organization_id: Annotated[
        str,
        "The ID of the organization to update the board classification for. Required for enterprise users with Company Admin rights.",  # noqa: E501
    ],
    team_id: Annotated[
        str,
        "The unique identifier of the team associated with the board to be updated. Required for classification updates.",  # noqa: E501
    ],
    data_classification_label_id: Annotated[
        str | None, "The ID of the data classification label to apply to the board."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-dataclassification-board-set'."
]:
    """Update the data classification for a Miro board.

    Use this tool to update the classification of a board within the Miro platform. This is an enterprise-only feature requiring Company Admin rights and the 'boards:write' scope."""  # noqa: E501
    request_data = remove_none_values({"labelId": data_classification_label_id})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/boards/{board_id}/data-classification".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, board_id=board_identifier
        ),
        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-miro"))
async def retrieve_ediscovery_cases(
    context: ToolContext,
    organization_id: Annotated[
        str, "The ID of the organization to retrieve the eDiscovery cases for."
    ],
    maximum_items: Annotated[
        int, "Specifies the maximum number of eDiscovery cases to retrieve in the result list."
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "Indicator for result position in pagination. Leave empty for first page; use previous request's cursor for subsequent pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-all-cases'."]:
    """Retrieve eDiscovery cases for your organization.

    Use this tool to obtain a list of eDiscovery cases available within your organization. This is specifically for Enterprise plan users with the Enterprise Guard add-on. Ensure you have the Company Admin and eDiscovery Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/cases".format(org_id=organization_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"limit": maximum_items, "cursor": 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-miro"))
async def retrieve_case_info(
    context: ToolContext,
    case_id: Annotated[str, "The unique ID of the case to retrieve information for."],
    organization_id: Annotated[
        str, "The unique ID of the organization whose case information you want to retrieve."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-case'."]:
    """Retrieve detailed information about an organization's case.

    Use this tool to obtain information about a specific case within an organization. Suitable for Enterprise plan users with required admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/cases/{case_id}".format(  # noqa: UP032
            org_id=organization_id, case_id=case_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-miro"))
async def retrieve_legal_holds(
    context: ToolContext,
    case_id: Annotated[
        str,
        "The ID of the case for which you want to retrieve the list of legal holds. Ensure it is a valid string identifying the case.",  # noqa: E501
    ],
    organization_id: Annotated[
        str,
        "The ID of the organization to retrieve the list of legal holds for a specific case. Required for identifying the organization.",  # noqa: E501
    ],
    maximum_items_in_result: Annotated[
        int, "Specify the maximum number of items to return in the result list."
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "An indicator of the result page position. Leave empty for the first page, or use the previous request's cursor value for subsequent pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-all-legal-holds'."]:
    """Retrieve all legal holds for an organization's case.

    This tool retrieves the list of all legal holds within a specified case for an organization. It is intended for Enterprise plan users with the Enterprise Guard add-on, requiring Company Admin and eDiscovery Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/cases/{case_id}/legal-holds".format(  # noqa: UP032
            org_id=organization_id, case_id=case_id
        ),
        method="GET",
        params=remove_none_values({"limit": maximum_items_in_result, "cursor": 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-miro"))
async def retrieve_legal_hold_info(
    context: ToolContext,
    case_identifier: Annotated[
        str, "The unique ID of the case for which you want to retrieve the legal hold information."
    ],
    legal_hold_identifier: Annotated[
        str, "The unique identifier for the legal hold you want to retrieve information about."
    ],
    organization_id: Annotated[
        str, "The unique ID of the organization to retrieve the legal hold information."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-legal-hold'."]:
    """Retrieve legal hold information for a specific case.

    This tool retrieves details of a legal hold within a case for an organization. It's designed for organizations with the Enterprise Guard add-on, and accessible to users with Company Admin and eDiscovery Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/cases/{case_id}/legal-holds/{legal_hold_id}".format(  # noqa: UP032
            org_id=organization_id, case_id=case_identifier, legal_hold_id=legal_hold_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-miro"))
async def review_legal_hold_boards(
    context: ToolContext,
    case_id: Annotated[
        str,
        "The unique identifier for the case associated with the legal hold items you wish to retrieve.",  # noqa: E501
    ],
    legal_hold_identifier: Annotated[
        str, "The unique identifier for the legal hold to retrieve content items under hold."
    ],
    organization_id: Annotated[
        str, "The ID of the organization to retrieve the list of content items under hold."
    ],
    maximum_items_in_result: Annotated[
        int,
        "The maximum number of items to include in the result list. Use to limit response size.",
    ] = 100,
    page_cursor: Annotated[
        str | None,
        "An indicator for pagination. Leave empty for the first page or use a value from the previous request's cursor field for next pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-legal-hold-content-items'."]:
    """Review Miro boards under legal hold for legal proceedings.

    This tool retrieves all Miro board content items under a specific legal hold for an organization. Use it to ensure relevant data is preserved for legal processes. The legal hold must be in an 'ACTIVE' state and available only to Enterprise plan users with appropriate admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/cases/{case_id}/legal-holds/{legal_hold_id}/content-items".format(  # noqa: UP032
            org_id=organization_id, case_id=case_id, legal_hold_id=legal_hold_identifier
        ),
        method="GET",
        params=remove_none_values({"limit": maximum_items_in_result, "cursor": page_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-miro"))
async def create_board_export_job(
    context: ToolContext,
    board_export_request_id: Annotated[
        str,
        "A unique identifier for the export job, used to track the export process of boards in Miro.",  # noqa: E501
    ],
    organization_id: Annotated[str, "Unique identifier of the organization for exporting boards."],
    export_board_ids: Annotated[
        list[str] | None,
        "A list of board IDs to be exported. Provide the IDs as an array of strings.",
    ] = None,
    export_format: Annotated[
        str | None,
        "Specifies the format for exporting the board. Options: SVG (default), HTML, or PDF.",
    ] = "SVG",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-create-board-export'."]:
    """Initiates an export job for specified boards in an organization.

    This tool creates an export job for one or more boards within Miro, available exclusively to Enterprise plan users with Company Admin roles and eDiscovery enabled. It's suitable for managing board exports, optimizing data handling within an organization."""  # noqa: E501
    request_data = remove_none_values({"boardIds": export_board_ids, "boardFormat": export_format})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/boards/export/jobs".format(  # noqa: UP032
            org_id=organization_id
        ),
        method="POST",
        params=remove_none_values({"request_id": board_export_request_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-miro"))
async def get_export_job_status(
    context: ToolContext,
    board_export_job_id: Annotated[str, "Unique identifier for the Miro board export job."],
    organization_id: Annotated[
        str,
        "Unique identifier for the Miro organization. Required for retrieving the export job status.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-board-export-job-status'."
]:
    """Retrieve the status of a Miro board export job.

    This tool checks the status of a board export job for Miro Enterprise plan users. It requires the user to be a Company Admin with eDiscovery enabled. Access is limited to users with the appropriate role and access privileges."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/boards/export/jobs/{job_id}".format(  # noqa: UP032
            org_id=organization_id, job_id=board_export_job_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-miro"))
async def retrieve_miro_export_results(
    context: ToolContext,
    job_identifier: Annotated[
        str, "Unique identifier for the Miro board export job, required to retrieve export results."
    ],
    organization_unique_identifier: Annotated[
        str, "The unique identifier for the organization. Required to retrieve export job results."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-board-export-job-results'."
]:
    """Retrieve results of a Miro board export job.

    This tool retrieves the results of an export job for a Miro board, available to Enterprise plan users. It provides detailed information, including an S3 link to the exported files. Ensure you have Company Admin rights and eDiscovery enabled to access this data."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/boards/export/jobs/{job_id}/results".format(  # noqa: UP032
            org_id=organization_unique_identifier, job_id=job_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-miro"))
async def fetch_board_content_changes(
    context: ToolContext,
    end_modification_datetime: Annotated[
        str,
        "Specify the end date and time for filtering content logs based on when a board item was last modified. Use UTC format adhering to ISO 8601 with a trailing Z offset.",  # noqa: E501
    ],
    organization_id: Annotated[
        str,
        "A string representing the unique identifier of the organization required for fetching board content changes.",  # noqa: E501
    ],
    start_date_time: Annotated[
        str,
        "Specify the start date and time for filtering content logs, in UTC format (ISO 8601 with trailing Z).",  # noqa: E501
    ],
    board_ids: Annotated[
        list[str] | None,
        "List of board IDs for retrieving content logs. Provide as an array of strings.",
    ] = None,
    max_results_per_call: Annotated[
        int | None,
        "The maximum number of results to return per call. If exceeded, a cursor is provided for pagination.",  # noqa: E501
    ] = 1000,
    pagination_cursor: Annotated[
        str | None,
        "Cursor for pagination to fetch the next set of results. Use the cursor value from the previous response to continue retrieving paginated results.",  # noqa: E501
    ] = None,
    sort_order_by_date: Annotated[
        str | None,
        "Specify 'asc' for ascending or 'desc' for descending sort order based on modified date.",
    ] = "asc",
    user_email_filter: Annotated[
        list[str] | None,
        "Filter content logs based on the list of emails of users who created, modified, or deleted board items.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-board-content-item-logs-fetch'."
]:
    """Fetches content changes for board items in your organization.

    Use this tool to retrieve content changes made to board items in your organization, including updates, creations, and deletions by users. You can filter the results by time period, board IDs, and user emails, and paginate the data for processing. Note: This tool is for Enterprise plan users and requires Company Admin role."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/content-logs/items".format(  # noqa: UP032
            org_id=organization_id
        ),
        method="GET",
        params=remove_none_values({
            "board_ids": board_ids,
            "emails": user_email_filter,
            "from": start_date_time,
            "to": end_modification_datetime,
            "cursor": pagination_cursor,
            "limit": max_results_per_call,
            "sorting": sort_order_by_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-miro"))
async def reset_user_sessions(
    context: ToolContext,
    user_email_for_session_reset: Annotated[
        str,
        "Email ID of the user whose sessions need resetting. This will sign the user out from all devices.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-post-user-sessions-reset'."
]:
    """Reset all active Miro sessions for a specific user.

    This tool is used to immediately end all active Miro sessions for a specified user across all devices, requiring them to sign in again. It's useful for security reasons, such as when credentials are compromised or there's suspicious activity. This API is enterprise-only and requires admin privileges."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/sessions/reset_all",
        method="POST",
        params=remove_none_values({"email": user_email_for_session_reset}),
        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-miro"))
async def get_organization_info(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique identifier for the organization to retrieve information for. This is required to access the organization's details.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-organization'."]:
    """Retrieve detailed information about a specific organization.

    This tool fetches organization information for Enterprise plan users with the role of Company Admin. It requires the 'organizations:read' scope. Rate limiting is at Level 3."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}".format(org_id=organization_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-miro"))
async def get_organization_members(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique identifier for the organization to retrieve members from. Required for the operation.",  # noqa: E501
    ],
    include_only_active_members: Annotated[
        bool | None,
        "Set to true to include only active members in the response. Set to false to include all members, regardless of their status.",  # noqa: E501
    ] = None,
    license_type: Annotated[
        str | None,
        "Defines the type of license for the organization members to filter (e.g., full, occasional, free).",  # noqa: E501
    ] = None,
    member_role_filter: Annotated[
        str | None,
        "Filter organization members by role, such as 'organization_internal_admin' or 'organization_external_user'.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "String value for pagination to retrieve the next set of results in a multi-page response.",
    ] = None,
    result_limit: Annotated[
        int | None, "Specifies the maximum number of organization members to retrieve."
    ] = 100,
    user_emails: Annotated[
        str | None, "A comma-separated string of user emails to filter organization members."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-get-organization-members'."
]:
    """Retrieve organization members using organization ID or emails.

    This tool retrieves members of an organization in Miro based on the provided organization ID and cursor, or user emails. It requires the 'organizations:read' scope and is accessible only to Enterprise plan users with Company Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/members".format(org_id=organization_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "emails": user_emails,
            "role": member_role_filter,
            "license": license_type,
            "active": include_only_active_members,
            "cursor": pagination_cursor,
            "limit": result_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-miro"))
async def get_organization_member_info(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the organization whose member info is being retrieved."
    ],
    organization_member_id: Annotated[
        str, "ID of the organization member to retrieve information for."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-get-organization-member'."
]:
    """Retrieve details about a specific organization member.

    This tool retrieves information about a member of an organization within Miro. It's designed for use by users on the Enterprise plan with the role of Company Admin. Useful for obtaining member-specific information for administrative tasks."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id, member_id=organization_member_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-miro"))
async def create_miro_board(
    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 'create-board'."]:
    """Create a new board on Miro with specific settings.

    This tool allows you to create a new board in Miro by specifying its name and sharing policies. It is limited to creating up to 3 team boards for users on the free plan. Ensure you have the required 'boards:write' scope.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMIROBOARD"],
            "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["CREATEMIROBOARD"] + "\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["CREATEMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMIROBOARD"],
        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-miro"))
async def retrieve_user_boards(
    context: ToolContext,
    board_owner_id: Annotated[
        str | None,
        "Filter boards by the owner's user ID to view boards created by a specific user.",
    ] = None,
    maximum_number_of_boards: Annotated[
        str | None,
        "Specifies the maximum number of boards to retrieve. Use a positive integer to limit results.",  # noqa: E501
    ] = None,
    project_id_filter: Annotated[
        str | None,
        "Specify the project ID to filter boards accessible to the user. This retrieves boards linked to the given project instantly.",  # noqa: E501
    ] = None,
    results_offset: Annotated[
        str | None,
        "The number of items to skip before starting to collect the result set. This is used for pagination.",  # noqa: E501
    ] = None,
    search_query: Annotated[
        str | None,
        "A string to search and filter boards by name or content. Useful for narrowing down results.",  # noqa: E501
    ] = None,
    sort_boards_by: Annotated[
        str | None,
        "Specify how to sort the list of boards. Options include 'default', 'last_modified', 'last_opened', 'last_created', and 'alphabetically'.",  # noqa: E501
    ] = "default",
    team_identifier: Annotated[
        str | None,
        "The unique identifier for the team to filter boards. This allows retrieval of boards associated with the specified team.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-boards'."]:
    """Retrieve a list of boards accessible to the user.

    Use this tool to get a list of Miro boards accessible to the user with the given access token. Supports filtering by team or project ID, and fetches boards instantly when filtered by these parameters. Ideal for users needing to view boards they have access to, including organizational admin users who need a broader view."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards",
        method="GET",
        params=remove_none_values({
            "team_id": team_identifier,
            "project_id": project_id_filter,
            "query": search_query,
            "owner": board_owner_id,
            "limit": maximum_number_of_boards,
            "offset": results_offset,
            "sort": sort_boards_by,
        }),
        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-miro"))
async def copy_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    source_board_id: Annotated[
        str | None,
        "Unique identifier (ID) of the board that you want to copy.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'copy-board'."]:
    """Create a copy of an existing Miro board.

    This tool creates a duplicate of an existing Miro board, allowing updates to the board's name, description, sharing policy, and permissions. It requires the 'boards:write' scope and complies with level 4 rate limiting.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["COPYMIROBOARD"],
            "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 required parameters
    missing_params = []
    if not source_board_id:
        missing_params.append(("source_board_id", "query"))

    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["COPYMIROBOARD"] + "\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["COPYMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards",
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["COPYMIROBOARD"],
        params=remove_none_values({"copy_from": source_board_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-miro"))
async def retrieve_board_info(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str, "Unique identifier (ID) of the board to retrieve information from Miro."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-specific-board'."]:
    """Retrieve details of a specific Miro board.

    Retrieve detailed information about a specific board in Miro, including metadata and content, using the board's unique identifier."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}".format(board_id=board_unique_identifier),  # 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-miro"))
async def update_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier of the Miro board to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-board'."]:
    """Update details of a specific Miro board.

    Use this tool to modify the settings or details of a particular board in Miro. Requires 'boards:write' scope.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMIROBOARD"],
            "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 board_identifier:
        missing_params.append(("board_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["UPDATEMIROBOARD"] + "\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["UPDATEMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}".format(board_id=board_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMIROBOARD"],
        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-miro"))
async def delete_miro_board(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the Miro board to be deleted. Must be a valid string ID."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-board'."]:
    """Delete a Miro board and move it to Trash.

    Use this tool to delete a Miro board. Deleted boards on paid plans are moved to Trash and can be restored via the UI within 90 days. Requires 'boards:write' scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}".format(board_id=board_identifier),  # 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-miro"))
async def add_app_card_to_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier (ID) of the Miro board where the app card will be added.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-app-card-item'."]:
    """Add an app card item to a specified board on Miro.

    Use this tool to add an app card item to a Miro board. Must have 'boards:write' scope permission. Appropriate for situations requiring interaction with Miro boards to add app-specific information cards.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDAPPCARDTOBOARD"],
            "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 board_identifier:
        missing_params.append(("board_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["ADDAPPCARDTOBOARD"] + "\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["ADDAPPCARDTOBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/app_cards".format(board_id=board_identifier),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDAPPCARDTOBOARD"],
        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-miro"))
async def retrieve_app_card_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "The unique ID for the board containing the app card item to retrieve."
    ],
    item_identifier: Annotated[
        str, "Unique identifier of the app card item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-app-card-item'."]:
    """Retrieve information for a specific Miro app card item.

    This tool retrieves detailed information about a specified app card item on a Miro board. Use it to access app card data when you have the board and item identifiers."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/app_cards/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=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-miro"))
async def update_miro_app_card(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier of the board where the app card will be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    item_identifier_for_update: Annotated[
        str | None,
        "Unique identifier (ID) of the app card item to update on the Miro board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-app-card-item'."]:
    """Update an app card item on a Miro board.

    Use this tool to update an existing app card on a Miro board based on specified data and style properties. Ensure you have the 'boards:write' scope.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMIROAPPCARD"],
            "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 board_identifier:
        missing_params.append(("board_identifier", "path"))
    if not item_identifier_for_update:
        missing_params.append(("item_identifier_for_update", "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["UPDATEMIROAPPCARD"] + "\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["UPDATEMIROAPPCARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/app_cards/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=item_identifier_for_update
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMIROAPPCARD"],
        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-miro"))
async def delete_app_card_from_board(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board from which to delete an app card item."
    ],
    item_id: Annotated[str, "Unique identifier (ID) of the item to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-app-card-item'."]:
    """Delete an app card item from a Miro board.

    Use this tool to delete a specific app card item from a Miro board. It's required when a card needs to be removed, ensuring the board is updated accordingly."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/app_cards/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=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-miro"))
async def add_card_to_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    miro_board_id: Annotated[
        str | None,
        "Unique identifier (ID) of the Miro board where you want to add the card item.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-card-item'."]:
    """Add a card item to a Miro board.

    Use this tool to create and add a new card item to a specified Miro board. This is useful for organizing tasks, notes, or ideas visually within a board. Ensure you have the required 'boards:write' scope for authorization.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDCARDTOMIROBOARD"],
            "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 miro_board_id:
        missing_params.append(("miro_board_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["ADDCARDTOMIROBOARD"] + "\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["ADDCARDTOMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/cards".format(board_id=miro_board_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDCARDTOMIROBOARD"],
        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-miro"))
async def get_card_item_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board to retrieve the specific card item from."
    ],
    item_unique_id: Annotated[str, "Unique identifier of the item to retrieve from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-card-item'."]:
    """Retrieve details about a specific card item from a Miro board.

    Use this tool to obtain information on a specific card item located on a Miro board. Ideal for accessing card details with read permissions on boards."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/cards/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=item_unique_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-miro"))
async def update_card_on_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier (ID) of the board on which the card item will be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    card_item_id: Annotated[
        str | None,
        "Unique identifier (ID) of the card item to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-card-item'."]:
    """Update a card item on a Miro board.

    This tool updates the properties and style of a specific card item on a Miro board. It should be called when you need to modify details or appearance of an existing card on a board. Requires 'boards:write' scope.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATECARDONBOARD"],
            "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 board_identifier:
        missing_params.append(("board_identifier", "path"))
    if not card_item_id:
        missing_params.append(("card_item_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["UPDATECARDONBOARD"] + "\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["UPDATECARDONBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/cards/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=card_item_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECARDONBOARD"],
        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-miro"))
async def delete_card_item(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board from which the card item should be deleted."
    ],
    item_id: Annotated[str, "The unique identifier of the card item to be deleted from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-card-item'."]:
    """Deletes a card item from the Miro board.

    Use this tool to delete a specific card item from a Miro board when its removal is required. It operates with 'boards:write' scope permissions and is subject to Level 3 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/cards/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=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-miro"))
async def add_connector_to_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_unique_id: Annotated[
        str | None,
        "Unique identifier for the Miro board where the connector will be added.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-connector'."]:
    """Adds a connector to a specified Miro board.

    Use this tool to add a connector to a specified Miro board. Requires 'boards:write' scope and is subject to Level 2 rate limiting.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDCONNECTORTOBOARD"],
            "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 board_unique_id:
        missing_params.append(("board_unique_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["ADDCONNECTORTOBOARD"] + "\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["ADDCONNECTORTOBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/connectors".format(board_id=board_unique_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDCONNECTORTOBOARD"],
        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-miro"))
async def get_board_connectors(
    context: ToolContext,
    board_identifier: Annotated[
        str,
        "Unique identifier for the Miro board to retrieve connectors from. Required for identifying the specific board.",  # noqa: E501
    ],
    pagination_cursor: Annotated[
        str | None,
        "Cursor pointing to the next set of results for paginated requests. Use this to retrieve subsequent pages.",  # noqa: E501
    ] = None,
    result_limit: Annotated[
        str | None,
        "Specifies the maximum number of connectors to retrieve in one call. This assists in pagination of connector data.",  # noqa: E501
    ] = "10",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-connectors'."]:
    """Retrieve connectors for a specified board on Miro.

    This tool retrieves a list of connectors for a specific board on Miro using a cursor-based approach. Useful for obtaining details about board connectors, with pagination support for large sets."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/connectors".format(  # noqa: UP032
            board_id=board_identifier
        ),
        method="GET",
        params=remove_none_values({"limit": result_limit, "cursor": 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-miro"))
async def retrieve_board_connector_info(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str, "Unique identifier for the Miro board from which to retrieve the connector."
    ],
    connector_unique_id: Annotated[
        str, "Unique identifier (ID) of the connector to retrieve from a Miro board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-connector'."]:
    """Retrieve information for a specific board connector.

    Use this tool to get detailed information about a specific connector on a Miro board. Ideal for extracting connector details when analyzing or processing board components."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/connectors/{connector_id}".format(  # noqa: UP032
            board_id=board_unique_identifier, connector_id=connector_unique_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-miro"))
async def update_connector_on_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "The unique string identifier of the Miro board to update the connector on.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    connector_identifier: Annotated[
        str | None,
        "Unique ID of the connector to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-connector'."]:
    """Update a connector on a Miro board.

    Updates a connector's properties and style on a specified Miro board using provided data. Requires 'boards:write' scope and adheres to Level 2 rate limiting.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATECONNECTORONBOARD"],
            "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 board_identifier:
        missing_params.append(("board_identifier", "path"))
    if not connector_identifier:
        missing_params.append(("connector_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["UPDATECONNECTORONBOARD"] + "\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["UPDATECONNECTORONBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/connectors/{connector_id}".format(  # noqa: UP032
            board_id=board_identifier, connector_id=connector_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECONNECTORONBOARD"],
        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-miro"))
async def delete_board_connector(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str, "Unique identifier (ID) of the board from which you want to delete the connector."
    ],
    connector_id: Annotated[str, "Unique identifier of the connector to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-connector'."]:
    """Delete a specific connector from a board.

    Use this tool to delete a specified connector from a Miro board. It requires write access to boards and is subject to Level 3 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/connectors/{connector_id}".format(  # noqa: UP032
            board_id=board_unique_identifier, connector_id=connector_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-miro"))
async def add_document_to_board_by_url(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the Miro board where the document will be added."
    ],
    document_title: Annotated[
        str | None, "A short text header to identify the document added to the board."
    ] = None,
    document_url: Annotated[
        str,
        "The URL where the document is hosted. This URL is required to add the document to the Miro board.",  # noqa: E501
    ] = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    item_height_in_pixels: Annotated[
        float | None, "Specifies the height of the item on the board in pixels."
    ] = None,
    item_width_in_pixels: Annotated[
        float | None, "Specify the width of the document item on the board in pixels."
    ] = None,
    parent_frame_id: Annotated[
        str | None, "Unique identifier of the parent frame for the document item on the board."
    ] = None,
    position_x_coordinate: Annotated[
        float | None,
        "X-axis coordinate for placing the item on the board. Defaults to `0`. Center of board is `0`.",  # noqa: E501
    ] = 0,
    rotation_angle_degrees: Annotated[
        float | None,
        "Specify the rotation angle of the document item in degrees. Use positive values for clockwise rotation and negative for counterclockwise.",  # noqa: E501
    ] = None,
    y_axis_coordinate_on_board: Annotated[
        float | None,
        "Y-axis coordinate for placing the document on the Miro board. Defaults to `0`, where `0` is the center of the board.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-document-item-using-url'."]:
    """Add a document to a Miro board using its URL.

    This tool adds a document as an item to a specified Miro board by providing the document's URL. It's useful for organizing resources in a collaborative board. Requires 'boards:write' scope and observes Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"title": document_title, "url": document_url},
        "position": {"x": position_x_coordinate, "y": y_axis_coordinate_on_board},
        "geometry": {
            "height": item_height_in_pixels,
            "width": item_width_in_pixels,
            "rotation": rotation_angle_degrees,
        },
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/documents".format(board_id=board_identifier),  # noqa: UP032
        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-miro"))
async def retrieve_document_item_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board to retrieve a specific document item from."
    ],
    document_item_id: Annotated[
        str, "Unique identifier (ID) of the document item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-document-item'."]:
    """Retrieve information for a specific document item on a board.

    Use this tool to obtain details about a specific document item on a Miro board. Ensure the required scope 'boards:read' is available. Ideal for when you need information about documents within board workflows."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/documents/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=document_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-miro"))
async def update_document_item_on_board(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier of the Miro board where the document item will be updated."
    ],
    item_identifier_to_update: Annotated[
        str, "Unique identifier of the item to update on the board."
    ],
    document_hosting_url: Annotated[
        str | None, "URL where the document is hosted for updating on the Miro board."
    ] = None,
    document_title: Annotated[
        str | None, "A short text header to identify the document on the board."
    ] = None,
    item_height_pixels: Annotated[float | None, "Height of the document item in pixels."] = None,
    item_width_pixels: Annotated[
        float | None, "Specify the width of the item in pixels on the board."
    ] = None,
    parent_frame_id: Annotated[
        str | None, "Unique identifier (ID) of the parent frame for the item on the board."
    ] = None,
    rotation_angle_degrees: Annotated[
        float | None,
        "Rotation angle of an item, in degrees, relative to the board. Positive values rotate clockwise, negative values rotate counterclockwise.",  # noqa: E501
    ] = None,
    x_axis_coordinate_on_board: Annotated[
        float | None,
        "X-axis coordinate for the item's placement on the board, with default positioning at 0. Center of the board is x: 0.",  # noqa: E501
    ] = 0,
    y_axis_coordinate: Annotated[
        float | None,
        "Y-axis coordinate for placing the item on the board. Default is 0, with absolute positioning relative to the board center.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-document-item-using-url'."]:
    """Update a document item on a Miro board using its URL.

    This tool updates a specific document item on a Miro board. It should be called when you need to modify the content or metadata of an item on a Miro board by using its URL. Ensure you have the required 'boards:write' scope and be mindful of Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"title": document_title, "url": document_hosting_url},
        "position": {"x": x_axis_coordinate_on_board, "y": y_axis_coordinate},
        "geometry": {
            "height": item_height_pixels,
            "width": item_width_pixels,
            "rotation": rotation_angle_degrees,
        },
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/documents/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=item_identifier_to_update
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_document_item_from_board(
    context: ToolContext,
    board_id: Annotated[str, "The unique ID of the Miro board to delete the document item from."],
    item_id: Annotated[str, "Unique identifier of the item to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-document-item'."]:
    """Deletes a document item from a Miro board.

    Use this tool to delete a specific document item from a Miro board. Ideal for cleaning up or managing board content. Requires 'boards:write' scope for permission."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/documents/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=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-miro"))
async def add_embed_item_to_board(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board where you want to create the embed item."
    ],
    content_display_mode: Annotated[
        str | None,
        "Specify how the embedded content is displayed on the board. Options are 'inline' for direct display and 'modal' for modal overlay.",  # noqa: E501
    ] = None,
    content_url: Annotated[
        str,
        "A valid URL pointing to the content resource to embed in the board. Supports HTTP and HTTPS.",  # noqa: E501
    ] = "https://www.youtube.com/watch?v=HlVSNEiFCBk",
    embed_item_width_pixels: Annotated[
        float | None,
        "Width of the embed item in pixels. Defines the size of the item on the board.",
    ] = None,
    item_height: Annotated[
        float | None, "Specify the height of the embedded item, in pixels."
    ] = None,
    parent_frame_id: Annotated[
        str | None, "Unique identifier (ID) of the parent frame for the item on the Miro board."
    ] = None,
    preview_image_url: Annotated[
        str | None, "URL of the image used as the preview for the embedded item."
    ] = None,
    x_coordinate_on_board: Annotated[
        float | None,
        "X-axis coordinate for the item's location on the board. Default is 0, with absolute positioning relative to the board center.",  # noqa: E501
    ] = 0,
    y_axis_coordinate_on_board: Annotated[
        float | None,
        "Y-axis coordinate for placing the item on the board. Default is 0, with (0, 0) as the board's center.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-embed-item'."]:
    """Add an embed item with external content to a Miro board.

    Use this tool to add external content as an embed item to a specific board on Miro. Requires 'boards:write' scope and is subject to rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"mode": content_display_mode, "previewUrl": preview_image_url, "url": content_url},
        "position": {"x": x_coordinate_on_board, "y": y_axis_coordinate_on_board},
        "geometry": {"height": item_height, "width": embed_item_width_pixels},
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/embeds".format(board_id=board_identifier),  # noqa: UP032
        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-miro"))
async def get_embed_item_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the Miro board from which to retrieve the embed item."
    ],
    embed_item_id: Annotated[
        str, "Unique identifier of the embed item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-embed-item'."]:
    """Retrieve details of an embed item on a Miro board.

    Use this tool to obtain information about a specific embed item on a Miro board. It's ideal when you need to fetch details of an embedded component such as a document or link integrated into a board. Ensure the board's read permissions are granted."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/embeds/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=embed_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-miro"))
async def update_embed_item_on_board(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board where you want to update the item."
    ],
    embed_item_id: Annotated[
        str, "Unique identifier (ID) of the embed item to update on the board."
    ],
    content_resource_url: Annotated[
        str | None,
        "A valid URL pointing to the content resource to embed in the board. Supports HTTP and HTTPS protocols.",  # noqa: E501
    ] = None,
    embed_display_mode: Annotated[
        str | None,
        "Defines how the content in the embed item is displayed on the board. Use 'inline' for direct display and 'modal' for a modal overlay view.",  # noqa: E501
    ] = None,
    item_height_in_pixels: Annotated[
        float | None, "Specify the height of the embed item in pixels."
    ] = None,
    item_width_pixels: Annotated[
        float | None, "Specifies the width of the item on the board in pixels."
    ] = None,
    parent_frame_id: Annotated[
        str | None,
        "Unique identifier (ID) of the parent frame for the item being updated on the board.",
    ] = None,
    preview_image_url: Annotated[
        str | None, "URL of the image used as the preview for the embedded item."
    ] = None,
    x_axis_coordinate_on_board: Annotated[
        float | None,
        "X-axis coordinate for the location of the item on the board. Defaults to `0`, representing the center of the board.",  # noqa: E501
    ] = 0,
    y_axis_coordinate: Annotated[
        float | None,
        "Y-axis coordinate of the item's location on the board. Defaults to `0`. Center of board is `y: 0`.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-embed-item'."]:
    """Update an embed item on a Miro board.

    Use this tool to update specific embed items on a Miro board. Ensure you have the required 'boards:write' scope before calling this tool. This action involves modifying the properties of an embed on the board and is subject to Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {
            "mode": embed_display_mode,
            "previewUrl": preview_image_url,
            "url": content_resource_url,
        },
        "position": {"x": x_axis_coordinate_on_board, "y": y_axis_coordinate},
        "geometry": {"height": item_height_in_pixels, "width": item_width_pixels},
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/embeds/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=embed_item_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def remove_embed_item_from_board(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the board from which the embed item will be deleted."
    ],
    item_id_to_delete: Annotated[
        str, "Unique identifier of the embed item to be deleted from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-embed-item'."]:
    """Remove an embed item from a Miro board.

    This tool deletes a specific embed item from a Miro board. It should be called when there's a need to remove an embedded element from a board, such as cleaning up or updating board contents."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/embeds/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=item_id_to_delete
        ),
        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-miro"))
async def add_image_to_miro_board(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str, "The unique identifier for the board where the image will be added."
    ],
    image_height_pixels: Annotated[
        float | None, "Height of the image in pixels to be added to the Miro board."
    ] = None,
    image_title: Annotated[
        str | None, "A short text header to identify the image on the Miro board."
    ] = None,
    image_url: Annotated[
        str, "URL of the image to be added to the Miro board."
    ] = "https://miro.com/static/images/page/mr-index/localization/en/slider/ideation_brainstorming.png",
    image_width_pixels: Annotated[
        float | None, "The width of the image in pixels to be added to the board."
    ] = None,
    parent_frame_id: Annotated[
        str | None,
        "Unique identifier for the parent frame where the image will be added. Use this to specify a frame within the board if needed.",  # noqa: E501
    ] = None,
    rotation_angle_degrees: Annotated[
        float | None,
        "Rotation angle of the image, in degrees, relative to the board. Use positive for clockwise and negative for counterclockwise.",  # noqa: E501
    ] = None,
    x_axis_coordinate: Annotated[
        float | None,
        "X-axis coordinate for the image location on the Miro board. Center is at x: 0. Default is 0.",  # noqa: E501
    ] = 0,
    y_axis_coordinate: Annotated[
        float | None,
        "Y-axis coordinate of the item's location on the board. Default is 0, centered on the board.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-image-item-using-url'."]:
    """Add an image to a Miro board using a URL.

    This tool is used to add an image to a specific Miro board by providing an image URL. It requires the 'boards:write' scope for authorization. Ideal for users who want to enhance their Miro boards with visual content by specifying image links."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"title": image_title, "url": image_url},
        "position": {"x": x_axis_coordinate, "y": y_axis_coordinate},
        "geometry": {
            "height": image_height_pixels,
            "width": image_width_pixels,
            "rotation": rotation_angle_degrees,
        },
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/images".format(  # noqa: UP032
            board_id=board_unique_identifier
        ),
        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-miro"))
async def get_image_item_info(
    context: ToolContext,
    board_unique_id: Annotated[
        str, "Unique identifier of the board to retrieve a specific image item from."
    ],
    image_item_id: Annotated[
        str, "Unique identifier of the image item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-image-item'."]:
    """Fetches details for a specified image item on a Miro board.

    Use this tool to retrieve detailed information for a specific image item within a Miro board. It requires the board ID and image item ID to access the data."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/images/{item_id}".format(  # noqa: UP032
            board_id=board_unique_id, item_id=image_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-miro"))
async def update_board_image(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the board where the image item will be updated."
    ],
    image_item_id: Annotated[str, "The unique ID of the image item to update on the board."],
    image_height_pixels: Annotated[
        float | None, "Specify the height of the image item in pixels."
    ] = None,
    image_title: Annotated[
        str | None, "A short text header to identify the image on the board."
    ] = None,
    image_url: Annotated[str | None, "The URL of the image to update on the board."] = None,
    image_width_in_pixels: Annotated[
        float | None, "The width of the image item on the board, specified in pixels."
    ] = None,
    item_rotation_angle: Annotated[
        float | None,
        "Specify the rotation angle for the image item in degrees. Use positive values for clockwise and negative for counterclockwise rotation.",  # noqa: E501
    ] = None,
    parent_frame_id: Annotated[
        str | None, "Unique identifier (ID) of the parent frame for the item."
    ] = None,
    x_axis_coordinate: Annotated[
        float | None,
        "X-axis coordinate for the item's location on the board. The board's center is at x: 0. Default is 0.",  # noqa: E501
    ] = 0,
    y_axis_coordinate_position: Annotated[
        float | None,
        "The Y-axis coordinate for placing the item on the board. Default is `0`, with absolute positioning on the board.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-image-item-using-url'."]:
    """Update an image item on a Miro board using a URL.

    Use this tool to update an existing image item on a specific Miro board by providing the image URL. Requires 'boards:write' scope and is subject to Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"title": image_title, "url": image_url},
        "position": {"x": x_axis_coordinate, "y": y_axis_coordinate_position},
        "geometry": {
            "height": image_height_pixels,
            "width": image_width_in_pixels,
            "rotation": item_rotation_angle,
        },
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/images/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=image_item_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_board_image(
    context: ToolContext,
    board_identifier: Annotated[
        str,
        "Unique identifier of the board from which to delete the image item. Required for deleting the image.",  # noqa: E501
    ],
    image_item_id: Annotated[str, "Unique identifier of the image item to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-image-item'."]:
    """Deletes an image item from a Miro board.

    Use this tool to delete an image from a specific Miro board by providing the board ID and image item ID. Ensure you have the required 'boards:write' scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/images/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=image_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-miro"))
async def retrieve_board_items(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the Miro board to retrieve items from."
    ],
    item_type_filter: Annotated[
        str | None,
        "Specify the type of items to retrieve from the board (e.g., 'text', 'shape', 'sticky_note').",  # noqa: E501
    ] = None,
    items_limit: Annotated[
        str | None,
        "The maximum number of items to retrieve from the board at once, using pagination.",
    ] = "10",
    pagination_cursor: Annotated[
        str | None,
        "A string token used for cursor-based pagination to fetch the next set of results.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-items'."]:
    """Retrieve items from a specific Miro board.

    Use this tool to get a list of items from a Miro board. You can retrieve all items, child items, or specific types of items. Results are retrieved using a cursor-based pagination system."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/items".format(board_id=board_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "limit": items_limit,
            "type": item_type_filter,
            "cursor": 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-miro"))
async def get_board_item_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board to retrieve the specific item from."
    ],
    item_identifier: Annotated[str, "Unique identifier of the item to retrieve from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-specific-item'."]:
    """Retrieve information for a specific item on a Miro board.

    Use this tool to get detailed information about a specific item on a Miro board using the provided board and item IDs."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/items/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=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-miro"))
async def update_item_position_parent(
    context: ToolContext,
    board_identifier: Annotated[
        str, "The unique ID of the board where the item's position or parent will be updated."
    ],
    item_id: Annotated[str, "Unique identifier of the item to update on the board."],
    item_x_axis_coordinate: Annotated[
        float | None,
        "X-axis coordinate for the item's location on the board. Default is 0, with positioning relative to the board center.",  # noqa: E501
    ] = 0,
    parent_frame_id: Annotated[
        str | None, "Unique identifier of the parent frame for the specified item."
    ] = None,
    y_axis_coordinate: Annotated[
        float | None,
        "Y-axis coordinate for the item's location on the board. Defaults to `0` with absolute positioning to the board, not the viewport.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-item-position-or-parent'."]:
    """Update an item's position or parent on a Miro board.

    Use this tool to update the position or change the parent of an item on a Miro board. This requires the 'boards:write' scope and adheres to level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "parent": {"id": parent_frame_id},
        "position": {"x": item_x_axis_coordinate, "y": y_axis_coordinate},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/items/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=item_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_board_item(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board from which you want to delete the item."
    ],
    item_id: Annotated[str, "Unique identifier (ID) of the item to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-item'."]:
    """Deletes an item from a Miro board.

    Use this tool to delete an item from a Miro board. It requires the 'boards:write' scope and is subject to Level 3 rate limiting. Ideal for managing board content."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/items/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=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-miro"))
async def share_miro_board(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str,
        "Unique identifier for the Miro board you want to share. Required to specify the target board.",  # noqa: E501
    ],
    invitee_email_addresses: Annotated[
        list[str], "A list of up to 20 email addresses to invite to the board."
    ],
    board_member_role: Annotated[
        str | None,
        "Role assigned to the board member. Options are viewer, commenter, editor, coowner, or owner. Note: 'owner' functions as 'coowner'.",  # noqa: E501
    ] = "commenter",
    invitation_message: Annotated[
        str | None,
        "A custom message to include in the invitation email sent to new board collaborators.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'share-board'."]:
    """Invite new members to collaborate on a Miro board.

    Use this tool to share a Miro board by sending invitation emails to new collaborators. Membership in the team might be required based on the board's sharing policy."""  # noqa: E501
    request_data = remove_none_values({
        "emails": invitee_email_addresses,
        "role": board_member_role,
        "message": invitation_message,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/members".format(  # noqa: UP032
            board_id=board_unique_identifier
        ),
        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-miro"))
async def get_miro_board_members(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the Miro board whose members are to be retrieved."
    ],
    number_of_members_to_retrieve: Annotated[
        str | None,
        "Specify the maximum number of board members to retrieve. This limits the number of results returned in one call.",  # noqa: E501
    ] = None,
    pagination_offset: Annotated[
        str | None,
        "Specifies the starting point of the list of board members to return, for pagination purposes.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-board-members'."]:
    """Retrieve members of a Miro board.

    This tool retrieves a pageable list of members for a specified Miro board. It should be called when you need to access information about who is part of a specific board. Ensure that the required scope 'boards:read' is authorized to use this tool."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/members".format(board_id=board_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "limit": number_of_members_to_retrieve,
            "offset": pagination_offset,
        }),
        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-miro"))
async def get_board_member_info(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board to which the board member belongs."
    ],
    board_member_id: Annotated[
        str,
        "Unique identifier (ID) of the board member whose role you want to retrieve on a specific board.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-specific-board-member'."]:
    """Retrieve details about a specific board member.

    Use this tool to get information about a specific member of a Miro board, including details necessary for collaboration and management. This should be called when you need to access a board member's data for a given board on Miro."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/members/{board_member_id}".format(  # noqa: UP032
            board_id=board_id, board_member_id=board_member_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-miro"))
async def update_board_member_role(
    context: ToolContext,
    board_member_unique_id: Annotated[
        str, "Unique identifier of the board member whose role needs updating."
    ],
    board_unique_identifier: Annotated[
        str, "Unique identifier for the board where the member role will be updated."
    ],
    board_member_role: Annotated[
        str | None,
        "The new role to assign to the board member. Options: 'viewer', 'commenter', 'editor', 'coowner', 'owner'.",  # noqa: E501
    ] = "commenter",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-board-member'."]:
    """Update the role of a Miro board member.

    This tool updates the role of a member on a Miro board. It should be called when you need to change a member's role on a specific board. Ensure you have the required 'boards:write' scope and consider rate limiting (Level 2) when using this tool."""  # noqa: E501
    request_data = remove_none_values({"role": board_member_role})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/members/{board_member_id}".format(  # noqa: UP032
            board_id=board_unique_identifier, board_member_id=board_member_unique_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def remove_board_member(
    context: ToolContext,
    board_member_id: Annotated[
        str, "Unique identifier of the board member to be removed from the board."
    ],
    board_unique_id: Annotated[
        str, "Unique identifier of the board from which the member will be removed."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'remove-board-member'."]:
    """Remove a member from a Miro board.

    Use this tool to remove a specific member from a Miro board. This action requires 'boards:write' scope. Useful for managing board access and maintaining privacy."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/members/{board_member_id}".format(  # noqa: UP032
            board_id=board_unique_id, board_member_id=board_member_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-miro"))
async def add_shape_to_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_id: Annotated[
        str | None,
        "Unique identifier of the Miro board where the shape will be added.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-shape-item'."]:
    """Add a shape to a Miro board.

    This tool is used to add a new shape item to a specified board on Miro. It requires valid board access permissions and helps in organizing visual content on the board.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDSHAPETOMIROBOARD"],
            "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 board_id:
        missing_params.append(("board_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["ADDSHAPETOMIROBOARD"] + "\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["ADDSHAPETOMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/shapes".format(board_id=board_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDSHAPETOMIROBOARD"],
        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-miro"))
async def get_shape_information(
    context: ToolContext,
    board_identifier: Annotated[
        str,
        "Unique identifier (ID) of the Miro board from which to retrieve a specific shape item.",
    ],
    shape_item_id: Annotated[
        str, "Unique identifier of the shape item to retrieve from the Miro board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-shape-item'."]:
    """Retrieve detailed information about a specific shape on a Miro board.

    Use this tool to obtain detailed information for a particular shape item on a Miro board. Useful for accessing shape attributes and configurations when given the board and item IDs."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=shape_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-miro"))
async def update_board_shape(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier of the Miro board where you want to update the shape item.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    shape_item_id: Annotated[
        str | None,
        "Unique identifier (ID) of the shape item to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-shape-item'."]:
    """Update a shape item on a Miro board.

    Use this tool to update the properties and style of a shape item on a specified Miro board. Necessary when changes to a shape's data or appearance are required.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBOARDSHAPE"],
            "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 board_identifier:
        missing_params.append(("board_identifier", "path"))
    if not shape_item_id:
        missing_params.append(("shape_item_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["UPDATEBOARDSHAPE"] + "\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["UPDATEBOARDSHAPE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=shape_item_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBOARDSHAPE"],
        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-miro"))
async def delete_shape_from_miro_board(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier of the board from which you want to delete the shape item."
    ],
    item_id: Annotated[
        str, "Unique identifier (ID) of the shape item to delete from the Miro board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-shape-item'."]:
    """Delete a shape item from a Miro board.

    Use this tool to delete a specific shape item from a Miro board using the board and item IDs. Ensure you have the necessary permissions and consider rate limits."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=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-miro"))
async def add_sticky_note_to_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_id: Annotated[
        str | None,
        "Unique identifier of the board where the sticky note will be added.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-sticky-note-item'."]:
    """Add a sticky note to a Miro board.

    Use this tool to create and add a sticky note item to a specified Miro board. This requires board write permissions and is subject to Level 2 rate limiting.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDSTICKYNOTETOBOARD"],
            "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 board_id:
        missing_params.append(("board_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["ADDSTICKYNOTETOBOARD"] + "\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["ADDSTICKYNOTETOBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/sticky_notes".format(board_id=board_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDSTICKYNOTETOBOARD"],
        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-miro"))
async def get_sticky_note_info(
    context: ToolContext,
    miro_board_id: Annotated[
        str, "The unique identifier of the Miro board to retrieve the sticky note item from."
    ],
    sticky_note_id: Annotated[
        str, "Specify the unique ID of the sticky note to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-sticky-note-item'."]:
    """Retrieve details of a sticky note from a Miro board.

    This tool retrieves information about a specific sticky note on a Miro board, using the provided board and item IDs."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/sticky_notes/{item_id}".format(  # noqa: UP032
            board_id=miro_board_id, item_id=sticky_note_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-miro"))
async def update_sticky_note(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_id_for_update: Annotated[
        str | None,
        "Unique identifier of the board where the sticky note update will occur.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    sticky_note_id: Annotated[
        str | None,
        "Unique identifier (ID) of the sticky note you want to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-sticky-note-item'."]:
    """Update a sticky note on a Miro board.

    Use this tool to update a sticky note item on a Miro board by providing the necessary data and style properties. Requires 'boards:write' scope. Suitable for instances where users need to modify sticky note contents or appearances remotely.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATESTICKYNOTE"],
            "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 board_id_for_update:
        missing_params.append(("board_id_for_update", "path"))
    if not sticky_note_id:
        missing_params.append(("sticky_note_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["UPDATESTICKYNOTE"] + "\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["UPDATESTICKYNOTE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/sticky_notes/{item_id}".format(  # noqa: UP032
            board_id=board_id_for_update, item_id=sticky_note_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESTICKYNOTE"],
        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-miro"))
async def delete_sticky_note(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board from which you want to delete the sticky note."
    ],
    sticky_note_id: Annotated[
        str, "Unique identifier (ID) of the sticky note you want to delete from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-sticky-note-item'."]:
    """Deletes a sticky note from a Miro board.

    Call this tool to remove a specific sticky note from a given Miro board by specifying the board and item IDs. Ensure you have the necessary 'boards:write' scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/sticky_notes/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=sticky_note_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-miro"))
async def add_text_to_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "The unique ID of the Miro board where the text item will be created.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-text-item'."]:
    """Add a text item to a specified Miro board.

    This tool adds a text item to a specified Miro board using the 'boards:write' scope. It's useful when you want to insert text elements into a Miro board for collaboration or documentation purposes. Ensure you have the necessary permissions and adhere to rate limits (Level 2).

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDTEXTTOMIROBOARD"],
            "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 board_identifier:
        missing_params.append(("board_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["ADDTEXTTOMIROBOARD"] + "\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["ADDTEXTTOMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/texts".format(board_id=board_identifier),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDTEXTTOMIROBOARD"],
        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-miro"))
async def get_text_item_info(
    context: ToolContext,
    board_id: Annotated[
        str,
        "Unique identifier (ID) of the board to retrieve a specific text item from. Required for identifying the board within Miro.",  # noqa: E501
    ],
    text_item_id: Annotated[
        str, "Unique identifier (ID) of the text item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-text-item'."]:
    """Retrieve details of a text item from a Miro board.

    Use this tool to get information about a specific text item on a Miro board, identified by board and item IDs."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/texts/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=text_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-miro"))
async def update_board_text(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_id: Annotated[
        str | None,
        "Unique ID of the Miro board where the text item is to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    text_item_id: Annotated[
        str | None,
        "Unique identifier (ID) of the text item to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-text-item'."]:
    """Update a text item on a Miro board.

    This tool updates a text item on a specified Miro board using provided data and style 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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBOARDTEXT"],
            "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 board_id:
        missing_params.append(("board_id", "path"))
    if not text_item_id:
        missing_params.append(("text_item_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["UPDATEBOARDTEXT"] + "\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["UPDATEBOARDTEXT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/texts/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=text_item_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBOARDTEXT"],
        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-miro"))
async def remove_board_text_item(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the board from which the text item will be deleted."
    ],
    text_item_id: Annotated[
        str, "Unique identifier (ID) of the text item to delete from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-text-item'."]:
    """Delete a text item from a Miro board.

    Use this tool to delete a specific text item from a board in Miro. Requires 'boards:write' scope. It's useful when you need to remove unwanted or obsolete text content from your boards."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/texts/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=text_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-miro"))
async def add_items_to_miro_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    miro_board_identifier: Annotated[
        str | None,
        "The unique identifier of the Miro board where items will be created.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-items'."]:
    """Add up to 20 items to a Miro board in one transaction.

    Use this tool to add multiple items like shapes, cards, and sticky notes to a Miro board in one operation. The operation is transactional; if adding any item fails, no items will be added. Suitable for enhancing board content efficiently.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDITEMSTOMIROBOARD"],
            "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 miro_board_identifier:
        missing_params.append(("miro_board_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["ADDITEMSTOMIROBOARD"] + "\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["ADDITEMSTOMIROBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/items/bulk".format(  # noqa: UP032
            board_id=miro_board_identifier
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDITEMSTOMIROBOARD"],
        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-miro"))
async def add_board_frame(
    context: ToolContext,
    board_id: Annotated[str, "Unique identifier of the board where the frame will be created."],
    frame_fill_color: Annotated[
        str | None,
        "Specify the fill color for the frame using hex values. Supported colors include: `#f5f6f8`, `#d5f692`, `#d0e17a`, `#93d275`, `#67c6c0`, `#23bfe7`, `#a6ccf5`, `#7b92ff`, `#fff9b1`, `#f5d128`, `#ff9d48`, `#f16c7f`, `#ea94bb`, `#ffcee0`, `#b384bb`, `#000000`. The default is transparent (`#ffffffff`).",  # noqa: E501
    ] = None,
    frame_format: Annotated[
        str | None, "Specify the format for the frame. Only 'custom' is supported."
    ] = "custom",
    frame_height_pixels: Annotated[
        float | None, "Specify the height of the frame in pixels on the Miro board."
    ] = None,
    frame_title: Annotated[
        str | None, "Title of the frame to appear at the top. It must be a string."
    ] = "Sample frame title",
    frame_type: Annotated[
        str | None,
        "Specify the type of frame to create. Only 'freeform' is supported at the moment.",
    ] = "freeform",
    frame_width_in_pixels: Annotated[
        float | None, "Specify the width of the frame in pixels."
    ] = None,
    position_y_coordinate: Annotated[
        float | None,
        "Y-axis coordinate for the frame's position on the board. Default is `0`. Center of the board has `x: 0` and `y: 0`.",  # noqa: E501
    ] = 0,
    reveal_frame_content: Annotated[
        bool | None,
        "Set to true to reveal content inside the frame; false to hide it. Applicable for Enterprise plans only.",  # noqa: E501
    ] = True,
    x_axis_position_on_board: Annotated[
        float | None,
        "X-axis coordinate for placing the frame on the board. Default is 0, where the center is at x: 0.",  # noqa: E501
    ] = 0,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-frame-item'."]:
    """Add a frame to a Miro board.

    Use this tool to add a new frame item to a specified Miro board. It requires the 'boards:write' scope to operate and adheres to Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "data": {
            "format": frame_format,
            "title": frame_title,
            "type": frame_type,
            "showContent": reveal_frame_content,
        },
        "style": {"fillColor": frame_fill_color},
        "position": {"x": x_axis_position_on_board, "y": position_y_coordinate},
        "geometry": {"height": frame_height_pixels, "width": frame_width_in_pixels},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/frames".format(board_id=board_id),  # noqa: UP032
        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-miro"))
async def get_board_frame_info(
    context: ToolContext,
    board_unique_id: Annotated[
        str, "Unique identifier of the board containing the frame to retrieve."
    ],
    frame_id: Annotated[str, "Unique identifier (ID) of the frame to retrieve from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-frame-item'."]:
    """Retrieve information about a specific frame on a board.

    Use this tool to get detailed information about a specific frame within a Miro board. Useful for accessing frame-specific data when managing or viewing board content."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/frames/{item_id}".format(  # noqa: UP032
            board_id=board_unique_id, item_id=frame_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-miro"))
async def update_miro_board_frame(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_id: Annotated[
        str | None,
        "Unique identifier (ID) of the board where the frame needs to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    frame_id: Annotated[
        str | None,
        "Unique identifier of the frame to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-frame-item'."]:
    """Update a frame on a Miro board with new properties.

    This tool updates a specific frame on a Miro board. It should be called when you want to modify a frame's data, style, or geometry. Ensure you have the required 'boards:write' scope. Rate limiting is Level 2.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEMIROBOARDFRAME"],
            "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 board_id:
        missing_params.append(("board_id", "path"))
    if not frame_id:
        missing_params.append(("frame_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["UPDATEMIROBOARDFRAME"] + "\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["UPDATEMIROBOARDFRAME"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/boards/{board_id}/frames/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=frame_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMIROBOARDFRAME"],
        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-miro"))
async def delete_board_frame(
    context: ToolContext,
    board_id: Annotated[
        str, "The unique ID of the Miro board from which you want to delete the frame."
    ],
    frame_id: Annotated[str, "Unique identifier of the frame to be deleted from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-frame-item'."]:
    """Delete a frame from a Miro board.

    Use this tool to delete a specific frame from a Miro board. Call this when you need to remove frame items identified by their board and item IDs. Requires 'boards:write' permission scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/frames/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=frame_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-miro"))
async def get_items_within_frame(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board containing the frame to retrieve items from."
    ],
    frame_id: Annotated[
        str,
        "The ID of the frame from which you want to retrieve the items. Required to locate the parent frame on the board.",  # noqa: E501
    ],
    item_retrieval_limit: Annotated[
        str | None,
        "Specifies the maximum number of items to return in one call. Used for pagination purposes.",  # noqa: E501
    ] = "10",
    item_type_filter: Annotated[
        str | None,
        "Specify the type of items to retrieve within the frame. Leave blank to retrieve all types.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None, "A string used for cursor-based pagination to fetch the next set of results."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-items-within-frame'."]:
    """Retrieve items within a specified frame on a Miro board.

    Use this tool to get a list of items located within a specific frame on a Miro board. It's useful for accessing grouped content within frames. The tool supports paginated results, using a cursor to fetch subsequent portions of data if the frame contains more items than the specified limit."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id_PlatformContainers}/items".format(  # noqa: UP032
            board_id_PlatformContainers=board_id
        ),
        method="GET",
        params=remove_none_values({
            "parent_item_id": frame_id,
            "limit": item_retrieval_limit,
            "type": item_type_filter,
            "cursor": 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-miro"))
async def get_app_usage_metrics(
    context: ToolContext,
    application_id: Annotated[
        str,
        "The ID of the app to retrieve metrics for. Provide a valid app ID to obtain usage data.",
    ],
    end_date: Annotated[str, "End date of the period in UTC format, e.g., 2024-12-31."],
    start_date_utc: Annotated[str, "Start date of the period in UTC format (e.g., 2024-12-31)."],
    group_data_by_period: Annotated[
        str | None, "Specify the time period for grouping data: 'DAY', 'WEEK', or 'MONTH'."
    ] = "WEEK",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-metrics'."]:
    """Retrieve usage metrics for a specific app over a time range.

    This tool is used to obtain usage metrics for a specific app within a defined time period. It requires an app management API token and the 'boards:read' scope. Useful for monitoring app performance and usage patterns."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/apps/{app_id}/metrics".format(  # noqa: UP032
            app_id=application_id
        ),
        method="GET",
        params=remove_none_values({
            "startDate": start_date_utc,
            "endDate": end_date,
            "period": group_data_by_period,
        }),
        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-miro"))
async def retrieve_app_metrics(
    context: ToolContext,
    app_id: Annotated[str, "The unique identifier of the app to retrieve total usage metrics for."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-metrics-total'."]:
    """Retrieve total usage metrics for a specific app.

    This tool retrieves total usage metrics for a specified app since its creation. It requires an app management API token and the appropriate scope. Useful for monitoring app usage and performance."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/apps/{app_id}/metrics-total".format(  # noqa: UP032
            app_id=app_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-miro"))
async def create_board_subscription(
    context: ToolContext,
    board_id: Annotated[
        str | None, "Unique identifier of the board to associate with the webhook subscription."
    ] = None,
    webhook_callback_url: Annotated[
        str | None,
        "The HTTPS URL where Miro sends a webhook upon an event occurrence. This URL must be accessible by Miro to receive notifications.",  # noqa: E501
    ] = None,
    webhook_status: Annotated[
        str | None,
        "Set the status of the webhook subscription. Use 'enabled' to receive notifications, 'disabled' to stop notifications, or 'lost_access' if access to the board is lost.",  # noqa: E501
    ] = "enabled",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-board-subscription'."]:
    """Subscribe to board update notifications via webhook.

    Creates a webhook subscription to get notifications when an item on a board is updated. Suitable for monitoring changes on a board except for tags, connectors, and comments. Requires 'boards:read' scope."""  # noqa: E501
    request_data = remove_none_values({
        "boardId": board_id,
        "callbackUrl": webhook_callback_url,
        "status": webhook_status,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/webhooks/board_subscriptions",
        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-miro"))
async def update_board_webhook_subscription(
    context: ToolContext,
    subscription_id: Annotated[
        str, "The unique identifier of the webhook subscription to be updated."
    ],
    webhook_callback_url: Annotated[
        str | None,
        "The HTTPS URL where Miro sends webhooks when events occur. Must be a valid URL.",
    ] = None,
    webhook_status: Annotated[
        str | None,
        "Set the webhook subscription status: `enabled`, `disabled`, or handle `lost_access`.",
    ] = "enabled",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-board-subscription'."]:
    """Update the status or URL of a board's webhook subscription.

    Use this tool to modify the status or the callback URL of an existing webhook subscription for a Miro board. This is useful for maintaining webhook integrations with updated configurations."""  # noqa: E501
    request_data = remove_none_values({
        "callbackUrl": webhook_callback_url,
        "status": webhook_status,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/webhooks/board_subscriptions/{subscription_id}".format(  # noqa: UP032
            subscription_id=subscription_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def get_user_webhook_subscriptions(
    context: ToolContext,
    pagination_cursor: Annotated[
        str | None,
        "A string token used to paginate through webhook subscriptions. If not provided, retrieves the first page.",  # noqa: E501
    ] = None,
    subscription_limit: Annotated[
        str | None, "Specify the maximum number of webhook subscriptions to retrieve for the user."
    ] = "10",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-user-subscriptions'."]:
    """Retrieve all webhook subscriptions for a Miro user.

    Use this tool to obtain details about webhook subscriptions associated with a specific Miro user. Useful for managing or inspecting active webhooks. Requires 'boards:read' scope and adheres to Level 4 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/webhooks/subscriptions",
        method="GET",
        params=remove_none_values({"limit": subscription_limit, "cursor": 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-miro"))
async def get_miro_subscription_info(
    context: ToolContext,
    subscription_id: Annotated[str, "Unique identifier of the Miro subscription to retrieve."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-subscription-by-id'."]:
    """Fetch detailed information for a specific Miro subscription.

    Use this tool to retrieve detailed information about a specific webhook subscription in Miro. It's useful for checking the status or configuration of a particular subscription. Requires 'boards:read' scope for access."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/webhooks/subscriptions/{subscription_id}".format(  # noqa: UP032
            subscription_id=subscription_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-miro"))
async def delete_miro_webhook_subscription(
    context: ToolContext,
    subscription_id: Annotated[
        str, "The unique identifier (ID) for the Miro subscription to delete."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-subscription-by-id'."]:
    """Delete a Miro webhook subscription by ID.

    Use this tool to delete a specific webhook subscription on Miro by providing its ID. This operation requires 'boards:read' scope and is subject to Level 2 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/webhooks/subscriptions/{subscription_id}".format(  # noqa: UP032
            subscription_id=subscription_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-miro"))
async def get_miro_mindmap_node(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the Miro board to retrieve a mind map node from."
    ],
    mindmap_node_id: Annotated[
        str, "Unique identifier of the mind map node to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-mindmap-node-experimental'."]:
    """Retrieve details about a specific mind map node on a Miro board.

    Use this tool to get information about a particular node within a mind map on a Miro board. Useful for understanding node details or extracting specific data related to mind map elements."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/mindmap_nodes/{item_id}".format(  # noqa: UP032
            board_id=board_id, item_id=mindmap_node_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-miro"))
async def delete_mindmap_node(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the board from which the mind map node will be deleted."
    ],
    mindmap_node_id: Annotated[
        str,
        "The unique ID of the mind map node to delete, including all child nodes, from the board.",
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-mindmap-node-experimental'."
]:
    """Delete a mind map node and its child nodes from the board.

    Use this tool to delete a specified mind map node and all its child nodes from a Miro board. This is suitable for when you need to clear or restructure parts of a mind map on the board. Requires 'boards:write' permission scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/mindmap_nodes/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=mindmap_node_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-miro"))
async def get_mindmap_nodes(
    context: ToolContext,
    board_identifier: Annotated[
        str,
        "The unique identifier for the Miro board to retrieve mind map nodes from. This ID specifies the target board.",  # noqa: E501
    ],
    maximum_results_limit: Annotated[
        str | None,
        "Specifies the maximum number of mind map nodes returned in the response. Use this to control pagination and limit the data load.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A string that points to the next portion of the results set for cursor-based pagination.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-mindmap-nodes-experimental'."]:
    """Retrieve mind map nodes from a specified Miro board.

    Use this tool to fetch a list of mind map nodes from a particular Miro board. Ideal for accessing structured information stored in mind maps. Requires board ID to specify the target board and supports cursor-based pagination for large datasets."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/mindmap_nodes".format(  # noqa: UP032
            board_id=board_identifier
        ),
        method="GET",
        params=remove_none_values({"limit": maximum_results_limit, "cursor": 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-miro"))
async def add_mindmap_node(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board where the new mind map node will be created."
    ],
    mindmap_node_content: Annotated[
        str | None, "The text content to display within the mind map node."
    ] = None,
    node_type: Annotated[
        str | None, "Specifies the type of mind map node. Use 'text' as the current valid type."
    ] = None,
    node_width_pixels: Annotated[
        float | None,
        "Width of the mind map node in pixels. Specifies how wide the node will appear on the board.",  # noqa: E501
    ] = None,
    parent_frame_id: Annotated[
        str | None, "Unique identifier (ID) of the parent node or frame for the mind map node."
    ] = None,
    x_coordinate: Annotated[
        float | None,
        "X-axis coordinate for node placement on the board. Defaults to `0`, placing the node at the center along the x-axis.",  # noqa: E501
    ] = 0,
    y_coordinate: Annotated[
        float | None,
        "Y-coordinate for item placement on the board. Defaults to 0 if not specified, placing it at the board's center.",  # noqa: E501
    ] = 0,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'create-mindmap-nodes-experimental'."
]:
    """Add a new mind map node to a Miro board.

    This tool adds a new node to a mind map on a Miro board. It can create root or child nodes with specified x, y coordinates for positioning. Use it when you want to expand a mind map with additional nodes on a specified board."""  # noqa: E501
    request_data = remove_none_values({
        "data": {"nodeView": {"data": {"type": node_type, "content": mindmap_node_content}}},
        "position": {"x": x_coordinate, "y": y_coordinate},
        "geometry": {"width": node_width_pixels},
        "parent": {"id": parent_frame_id},
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/mindmap_nodes".format(  # noqa: UP032
            board_id=board_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-miro"))
async def get_board_items(
    context: ToolContext,
    board_identifier: Annotated[str, "Unique identifier of the Miro board to retrieve items from."],
    item_type: Annotated[
        str | None, "Specify the type of items to retrieve from the board, such as 'shape'."
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "The cursor value to retrieve the next set of items from a board. Use the value returned in the previous response to paginate through results.",  # noqa: E501
    ] = None,
    pagination_limit: Annotated[
        str | None,
        "The maximum number of items to return in a single call. Use this to control the size of the result set per request.",  # noqa: E501
    ] = "10",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-items-experimental'."]:
    """Retrieve items from a specific Miro board.

    This tool retrieves items from a specific Miro board. It can fetch all items, child items within a parent, or specific types of items using query parameters. Results are paginated; use the cursor value from the previous response to get the next set of items."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/items".format(  # noqa: UP032
            board_id=board_identifier
        ),
        method="GET",
        params=remove_none_values({
            "limit": pagination_limit,
            "type": item_type,
            "cursor": 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-miro"))
async def retrieve_board_item_info(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier of the Miro board to retrieve a specific item from."
    ],
    item_id: Annotated[str, "Unique identifier of the item to retrieve from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-specific-item-experimental'."]:
    """Retrieve details for a specific board item on Miro.

    This tool retrieves information for a specific item on a Miro board. It should be called when you need details about an item identified by its board and item identifiers. Useful for accessing content or metadata of a board item."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/items/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=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-miro"))
async def remove_board_item(
    context: ToolContext,
    board_unique_id: Annotated[str, "Unique identifier of the Miro board to delete the item from."],
    item_id: Annotated[str, "Unique identifier (ID) of the item to delete from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-item-experimental'."]:
    """Delete an item from a Miro board.

    This tool deletes a specified item from a given Miro board. It requires the board's ID and the item's ID. This operation should be called when an item needs to be permanently removed from a board."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/items/{item_id}".format(  # noqa: UP032
            board_id=board_unique_id, item_id=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-miro"))
async def add_flowchart_shape_to_board(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "The unique ID of the Miro board where the flowchart shape will be added.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'create-shape-item-flowchart'."]:
    """Add a flowchart shape item to a Miro board.

    Use this tool to add a flowchart shape to a specific Miro board. This enables users to programmatically enhance their boards with flowchart items, requiring write access to the board. It's ideal for automating workflows or integrating with other tools that require visualization updates in Miro.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDFLOWCHARTSHAPETOBOARD"],
            "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 board_identifier:
        missing_params.append(("board_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["ADDFLOWCHARTSHAPETOBOARD"] + "\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["ADDFLOWCHARTSHAPETOBOARD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/shapes".format(  # noqa: UP032
            board_id=board_identifier
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDFLOWCHARTSHAPETOBOARD"],
        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-miro"))
async def retrieve_shape_information(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier for the board to retrieve a specific shape item from."
    ],
    shape_item_id: Annotated[
        str, "Unique identifier (ID) of the shape item to retrieve from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-shape-item-flowchart'."]:
    """Retrieve information for a specific shape item on a board.

    Use this tool to get detailed information about a specific shape item on a Miro board. It requires the 'boards:read' scope and is subject to level 1 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=shape_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-miro"))
async def update_flowchart_shape(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    board_identifier: Annotated[
        str | None,
        "Unique identifier of the board where the shape item will be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    item_id: Annotated[
        str | None,
        "Unique identifier (ID) of the shape item to update on the board.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'update-shape-item-flowchart'."]:
    """Update a shape item in a Miro flowchart board.

    Use this tool to update the data and style properties of a shape item on a Miro board flowchart. Ideal for modifying existing shapes with new information. Requires 'boards:write' scope.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEFLOWCHARTSHAPE"],
            "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 board_identifier:
        missing_params.append(("board_identifier", "path"))
    if not item_id:
        missing_params.append(("item_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["UPDATEFLOWCHARTSHAPE"] + "\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["UPDATEFLOWCHARTSHAPE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_identifier, item_id=item_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEFLOWCHARTSHAPE"],
        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-miro"))
async def delete_flowchart_shape(
    context: ToolContext,
    board_unique_id: Annotated[
        str, "Unique identifier of the board from which to delete the shape item."
    ],
    item_id: Annotated[
        str, "Unique identifier of the flowchart shape item to delete from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-shape-item-flowchart'."]:
    """Delete a flowchart shape from a Miro board.

    Use this tool to delete a specific flowchart shape item from a Miro board by specifying the board and item identifiers."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2-experimental/boards/{board_id}/shapes/{item_id}".format(  # noqa: UP032
            board_id=board_unique_id, item_id=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-miro"))
async def create_group_on_board(
    context: ToolContext,
    board_identifier: Annotated[
        str, "The unique string identifier of the Miro board where the group will be created."
    ],
    item_ids: Annotated[
        list[str] | None,
        "An array of unique identifiers (IDs) for the items to be grouped on the board. Each ID corresponds to an item you wish to include in the group.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'createGroup'."]:
    """Creates a group of items on a Miro board.

    This tool creates a group consisting of specified items on a Miro board. It should be used when you want to organize multiple items into a group on a board. Requires 'boards:write' scope."""  # noqa: E501
    request_data = remove_none_values({"data": {"items": item_ids}})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups".format(board_id=board_identifier),  # noqa: UP032
        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-miro"))
async def get_board_groups(
    context: ToolContext,
    board_id: Annotated[str, "Unique identifier of the Miro board to retrieve groups from."],
    max_items_to_return: Annotated[
        int | None,
        "The maximum number of items to return at once. Default is 10 and the maximum is 50.",
    ] = 10,
    next_page_cursor: Annotated[
        str | None,
        "The cursor value for fetching the next set of group items from the board. Use it to paginate results.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-all-groups'."]:
    """Retrieve all groups and their items from a specific board.

    Use this tool to get all the groups along with their respective items from a given board in Miro. The data is retrieved using a cursor-based pagination method. To fetch the next set of results, use the cursor value from the previous response."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups".format(board_id=board_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"limit": max_items_to_return, "cursor": next_page_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-miro"))
async def get_items_by_group_id(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the Miro board to retrieve items from."
    ],
    group_item_id: Annotated[str, "The ID of the group item to retrieve from the board."],
    max_items_per_request: Annotated[
        int | None,
        "The maximum number of items to return per request. Default is 10, maximum is 50.",
    ] = 10,
    next_cursor: Annotated[
        str | None,
        "A string used for cursor-based pagination to fetch the next set of results. Set it to the value received from the previous API response to continue retrieving items.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getItemsByGroupId'."]:
    """Retrieve items from a specific group on a Miro board.

    This tool retrieves a list of items that belong to any group within a specified Miro board. It uses a cursor-based pagination method to manage large sets of results. Use this tool to explore group-contained items on Miro boards when you have the appropriate board access permissions."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups/items".format(  # noqa: UP032
            board_id=board_identifier
        ),
        method="GET",
        params=remove_none_values({
            "limit": max_items_per_request,
            "cursor": next_cursor,
            "group_item_id": group_item_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-miro"))
async def get_group_items_miro(
    context: ToolContext,
    board_unique_identifier: Annotated[
        str, "Unique identifier for the Miro board to fetch group items from."
    ],
    group_id: Annotated[str, "Unique identifier of the group to retrieve items from on the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getGroupById'."]:
    """Retrieve items from a specific group on a Miro board.

    Call this tool to fetch a list of items within a specific group on a Miro board, identified by board_id and group_id. Useful for accessing group content details."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups/{group_id}".format(  # noqa: UP032
            board_id=board_unique_identifier, group_id=group_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-miro"))
async def ungroup_items_on_miro_board(
    context: ToolContext,
    group_id: Annotated[
        str, "Unique identifier (ID) of the group to be ungrouped on the Miro board."
    ],
    miro_board_id: Annotated[
        str, "Unique identifier (ID) of the Miro board to ungroup items from."
    ],
    remove_items_after_ungrouping: Annotated[
        bool | None, "Specify if items should be removed after ungrouping. Default is false."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'unGroup'."]:
    """Ungroup items from a group on Miro board.

    Use this tool to ungroup items from a specific group on a Miro board. This operation requires 'boards:write' scope and is subject to Level 3 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups/{group_id}".format(  # noqa: UP032
            board_id=miro_board_id, group_id=group_id
        ),
        method="DELETE",
        params=remove_none_values({"delete_items": remove_items_after_ungrouping}),
        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-miro"))
async def update_board_group(
    context: ToolContext,
    board_unique_id: Annotated[str, "Unique identifier for the board to update the group on."],
    group_id: Annotated[
        str,
        "Unique identifier (ID) of the group to be updated. This ID is required to replace the group.",  # noqa: E501
    ],
    item_ids: Annotated[
        list[str] | None, "Array of unique identifiers (IDs) for the items in the new group."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'updateGroup'."]:
    """Replace and update an existing group in a board.

    Use this tool to replace an entire group on a Miro board with a new one. The original group is completely replaced and assigned a new group ID each time. Ensure you have the required scope, 'boards:write', to perform this operation."""  # noqa: E501
    request_data = remove_none_values({"data": {"items": item_ids}})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups/{group_id}".format(  # noqa: UP032
            board_id=board_unique_id, group_id=group_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-miro"))
async def delete_board_group(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier of the board from which the group will be deleted."
    ],
    delete_items_with_group: Annotated[
        bool, "Set to true to delete items within the group when deleting the group from the board."
    ],
    group_id: Annotated[str, "Unique identifier (ID) of the group to be deleted."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'deleteGroup'."]:
    """Delete a group and its items from a Miro board.

    This tool removes a specified group and all its contained items from a Miro board. Use it when you need to clean up or reorganize board content by deleting groups. Ensure you have the necessary permissions to modify the board."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/groups/{group_id}?".format(  # noqa: UP032
            board_id=board_id, group_id=group_id
        ),
        method="DELETE",
        params=remove_none_values({"delete_items": delete_items_with_group}),
        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-miro"))
async def revoke_miro_access_token(
    context: ToolContext,
    client_id: Annotated[
        str,
        "The client ID associated with the access token for Miro. Required for token revocation.",
    ],
    client_secret: Annotated[
        str,
        "The client secret associated with the access token to be revoked. This is required for validation.",  # noqa: E501
    ],
    miro_access_token: Annotated[
        str,
        "The Miro access token that needs to be revoked, rendering it and the refresh token unusable.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'revoke-token-v2'."]:
    """Revoke the current Miro access token.

    This tool revokes the current access token for Miro, rendering both the access and refresh tokens invalid. It should be called when you need to terminate access to Miro resources. The application remains installed for the user, even after token revocation."""  # noqa: E501
    request_data = remove_none_values({
        "accessToken": miro_access_token,
        "clientId": client_id,
        "clientSecret": client_secret,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/oauth/revoke",
        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-miro"))
async def get_tags_from_item(
    context: ToolContext,
    board_identifier: Annotated[
        str,
        "Unique identifier (ID) of the board containing the item for which tags are to be retrieved.",  # noqa: E501
    ],
    item_identifier: Annotated[
        str, "Unique identifier (ID) of the item whose tags are to be retrieved from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-tags-from-item'."]:
    """Retrieve all tags from a specified item on a board.

    Use this tool to get a list of all tags associated with a specific item on a Miro board. This requires read access to the board via the appropriate scope and is subject to Level 1 rate limiting."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/items/{item_id}/tags".format(  # noqa: UP032
            board_id=board_identifier, item_id=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-miro"))
async def create_board_tag(
    context: ToolContext,
    board_unique_id: Annotated[
        str, "The unique identifier (ID) of the Miro board where the tag will be created."
    ],
    tag_title: Annotated[str, "Unique, case-sensitive text for the tag."],
    tag_fill_color: Annotated[
        str | None,
        "The fill color of the tag. Choose from options: red, light_green, cyan, yellow, magenta, green, blue, gray, violet, dark_green, dark_blue, black.",  # noqa: E501
    ] = "red",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'create-tag'."]:
    """Create a tag on a Miro board.

    This tool is used to create a tag on a specified Miro board. It should be called when there's a need to organize or categorize elements on a board by adding a new tag."""  # noqa: E501
    request_data = remove_none_values({"fillColor": tag_fill_color, "title": tag_title})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/tags".format(board_id=board_unique_id),  # noqa: UP032
        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-miro"))
async def get_board_tags(
    context: ToolContext,
    board_id: Annotated[
        str,
        "The unique identifier of the board to retrieve tags from. This is required to specify the target board in Miro.",  # noqa: E501
    ],
    maximum_number_of_tags: Annotated[
        str | None, "Specifies the maximum number of tags to retrieve from the board."
    ] = None,
    result_offset: Annotated[
        str | None,
        "Specifies the starting point for the result set to retrieve tags, useful for pagination.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-tags-from-board'."]:
    """Retrieve all tags from a specified Miro board.

    Use this tool to get all the tags associated with a specific board in Miro. It helps in organizing and filtering board elements by tags."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/tags".format(board_id=board_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"limit": maximum_number_of_tags, "offset": result_offset}),
        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-miro"))
async def get_tag_info(
    context: ToolContext,
    board_id: Annotated[
        str, "Unique identifier (ID) of the board from which to retrieve the specific tag."
    ],
    tag_identifier: Annotated[str, "Unique identifier of the tag to retrieve from the board."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-tag'."]:
    """Retrieve detailed information for a specific tag on a Miro board.

    Use this tool to obtain information about a specific tag associated with a Miro board. Useful for tasks requiring tag metadata or management on boards."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/tags/{tag_id}".format(  # noqa: UP032
            board_id=board_id, tag_id=tag_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-miro"))
async def update_miro_tag(
    context: ToolContext,
    board_identifier: Annotated[
        str, "Unique identifier (ID) of the board where the tag update should occur."
    ],
    tag_unique_identifier: Annotated[
        str, "Unique identifier for the tag you want to update on a Miro board."
    ],
    tag_fill_color: Annotated[
        str | None,
        "Specify the fill color for the tag. Choices are: red, light_green, cyan, yellow, magenta, green, blue, gray, violet, dark_green, dark_blue, black.",  # noqa: E501
    ] = None,
    tag_title: Annotated[
        str | None, "Unique, case-sensitive text of the tag to be updated."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'update-tag'."]:
    """Update a tag on a Miro board.

    This tool updates a specific tag on a Miro board using the provided board and tag IDs. Note that changes made via the API will not be visible in real time on the board; a page refresh is required to see updates."""  # noqa: E501
    request_data = remove_none_values({"fillColor": tag_fill_color, "title": tag_title})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/tags/{tag_id}".format(  # noqa: UP032
            board_id=board_identifier, tag_id=tag_unique_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_board_tag(
    context: ToolContext,
    board_identifier: Annotated[str, "Unique ID of the board from which to delete a specific tag."],
    tag_id_to_delete: Annotated[
        str, "Unique identifier of the tag that you want to delete from the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'delete-tag'."]:
    """Delete a tag from a Miro board and its items.

    This tool deletes a specified tag from a Miro board and removes it from all cards and sticky notes on the board. Note that changes via the API are not reflected in real-time and require a board refresh to update visually."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id}/tags/{tag_id}".format(  # noqa: UP032
            board_id=board_identifier, tag_id=tag_id_to_delete
        ),
        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-miro"))
async def retrieve_items_by_tag(
    context: ToolContext,
    board_id: Annotated[str, "Unique identifier of the board from which to retrieve items by tag."],
    tag_identifier: Annotated[
        str, "Unique identifier (ID) for the tag to retrieve items associated with it."
    ],
    max_items_to_retrieve: Annotated[
        str | None,
        "Specifies the maximum number of items to retrieve with the specified tag from a board. Use an integer.",  # noqa: E501
    ] = None,
    pagination_offset: Annotated[
        str | None,
        "Specifies the number of items to skip before starting to collect the result set. Use to navigate paginated results.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-items-by-tag'."]:
    """Retrieve items from a board by specifying a tag.

    Use this tool to get all items from a Miro board that are associated with a specific tag. It helps in organizing and obtaining items based on tag categorization within the board."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id_PlatformTags}/items".format(  # noqa: UP032
            board_id_PlatformTags=board_id
        ),
        method="GET",
        params=remove_none_values({
            "limit": max_items_to_retrieve,
            "offset": pagination_offset,
            "tag_id": tag_identifier,
        }),
        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-miro"))
async def attach_tag_to_item(
    context: ToolContext,
    board_id: Annotated[str, "Unique ID of the board where the item to tag is located."],
    item_identifier: Annotated[
        str, "Unique identifier of the item to which you want to add a tag on the Miro board."
    ],
    tag_id: Annotated[str, "Unique identifier of the tag to attach to the item."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'attach-tag-to-item'."]:
    """Attach a tag to a specific item on a Miro board.

    Use this tool to attach an existing tag to a specified item, such as a card or sticky note, on a Miro board. Note that updates to tags via the API will require a board refresh to be visible."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id_PlatformTags}/items/{item_id}".format(  # noqa: UP032
            board_id_PlatformTags=board_id, item_id=item_identifier
        ),
        method="POST",
        params=remove_none_values({"tag_id": tag_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-miro"))
async def remove_tag_from_item(
    context: ToolContext,
    board_id_for_tag_removal: Annotated[
        str, "Unique identifier (ID) of the board from which the tag will be removed from an item."
    ],
    item_id: Annotated[
        str, "Unique identifier (ID) of the item from which the tag will be removed."
    ],
    tag_unique_identifier: Annotated[
        str, "Unique identifier (ID) of the tag you want to remove from the item on the board."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'remove-tag-from-item'."]:
    """Remove a specified tag from an item on a Miro board.

    Use this tool to remove a specific tag from an item on a Miro board. The tag will still exist on the board, but it will no longer be associated with the item. Note that changes will not reflect in real-time on the board unless refreshed."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/boards/{board_id_PlatformTags}/items/{item_id}".format(  # noqa: UP032
            board_id_PlatformTags=board_id_for_tag_removal, item_id=item_id
        ),
        method="DELETE",
        params=remove_none_values({"tag_id": tag_unique_identifier}),
        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-miro"))
async def create_enterprise_project(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique ID of the organization where you want to create a project on Miro."
    ],
    project_name: Annotated[
        str,
        "The name of the project to be created within the team. It should be descriptive and unique for easy identification.",  # noqa: E501
    ],
    team_id: Annotated[str, "The unique ID of the team where the project will be created."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-create-project'."]:
    """Create a new project within an enterprise team on Miro.

    Use this tool to create a project in an existing team for Enterprise plan users on Miro. Projects help organize boards and manage user access for groups within a team. This tool requires Company Admin access on an Enterprise plan."""  # noqa: E501
    request_data = remove_none_values({"name": project_name})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects".format(  # noqa: UP032
            org_id=organization_id, team_id=team_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-miro"))
async def get_team_projects(
    context: ToolContext,
    organization_id: Annotated[
        str, "The ID of the organization to retrieve the list of available projects from."
    ],
    team_id: Annotated[
        str,
        "The ID of the team from which to retrieve the list of projects. This is required to specify which team's projects to fetch.",  # noqa: E501
    ],
    max_results_per_call: Annotated[
        int | None,
        "The maximum number of projects to return in one call. Use this to control the size of the dataset returned. If exceeded, a cursor for pagination will be provided.",  # noqa: E501
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "Used to navigate through pages of results. Leave empty for the first page; set to the value from the previous response for subsequent pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-projects'."]:
    """Fetches projects from a specified team within an organization.

    Use this tool to retrieve a list of all projects, including private ones, from a team in an organization. It requires Content Admin permissions and is available only to Enterprise plan users with Company Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id
        ),
        method="GET",
        params=remove_none_values({"limit": max_results_per_call, "cursor": 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-miro"))
async def get_miro_project_info(
    context: ToolContext,
    organization_id: Annotated[str, "The ID of the organization to retrieve project information."],
    project_identifier: Annotated[str, "The unique ID of the project to retrieve information for."],
    team_id_for_project_info: Annotated[
        str, "The ID of the team from which you want to retrieve the project information."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-project'."]:
    """Retrieve information for a specific Miro project.

    This tool retrieves information about a specific project in Miro, such as the project name. It is intended for users with Enterprise plans and requires the 'projects:read' scope. Use this tool when you need to view details of a specific project as an Enterprise user."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id_for_project_info, project_id=project_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-miro"))
async def update_project_info(
    context: ToolContext,
    new_project_name: Annotated[str, "New name to be assigned to the project."],
    organization_id: Annotated[str, "The unique identifier of an organization."],
    project_identifier: Annotated[str, "The unique identifier for the project to be updated."],
    team_id: Annotated[str, "The unique identifier for a team associated with the project."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-update-project'."]:
    """Update project details for an enterprise account.

    This tool updates project information such as name and description in enterprise accounts on Miro. It requires company admin privileges and is only available for Enterprise plan users."""  # noqa: E501
    request_data = remove_none_values({"name": new_project_name})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_enterprise_project(
    context: ToolContext,
    organization_id: Annotated[
        str, "The ID of the organization from which you want to delete a project."
    ],
    project_id_to_delete: Annotated[
        str, "The ID of the project that needs to be deleted from the organization."
    ],
    team_id: Annotated[
        str, "The unique identifier of the team from which the project is to be deleted."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-delete-project'."]:
    """Delete a project while retaining associated boards and users.

    Use this tool to delete a project in an enterprise Miro account. This action retains all boards and users under the same team. It requires 'projects:write' scope and is available only for enterprise users with Company Admin role."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_id_to_delete
        ),
        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-miro"))
async def get_project_settings(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique ID of the organization to which the project belongs."
    ],
    project_id: Annotated[
        str, "The ID of the project for which you want to retrieve the project settings."
    ],
    team_id: Annotated[
        str,
        "The ID of the team to which the project belongs. Must be a valid team ID within the organization.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-project-settings'."]:
    """Retrieve enterprise project settings for a specific project.

    This tool retrieves the settings of a specific project within an enterprise account on Miro. It is available only to Enterprise plan users with Company Admin roles. The tool should be called to obtain project-related configuration details, ensuring an understanding of current project settings."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_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-miro"))
async def update_project_settings(
    context: ToolContext,
    organization_id: Annotated[str, "The ID of the organization to which the project belongs."],
    project_identifier: Annotated[
        str, "The unique ID of the project whose settings need updating."
    ],
    team_id: Annotated[str, "The unique identifier of the team associated with the project."],
    team_access_level: Annotated[
        str | None,
        'Specifies the access level for the team. Use "private" to restrict access to project members only and "view" to allow team-wide viewing.',  # noqa: E501
    ] = "private",
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-update-project-settings'."
]:
    """Update settings for an enterprise-level project.

    This tool updates the settings of a specified project within an organization's team, available only for Enterprise plan users with Company Admin roles."""  # noqa: E501
    request_data = remove_none_values({"sharingPolicySettings": {"teamAccess": team_access_level}})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def add_miro_project_member(
    context: ToolContext,
    organization_id: Annotated[str, "The ID of the organization to which the project belongs."],
    project_id: Annotated[str, "The unique ID of the Miro project to which a user will be added."],
    project_member_role: Annotated[
        str,
        "Role of the user to be assigned in the Miro project. Possible values: owner, editor, viewer, commentator, coowner.",  # noqa: E501
    ],
    team_id: Annotated[
        str,
        "The ID of the team to which the project belongs. Used to identify the specific team for project member addition.",  # noqa: E501
    ],
    user_email_id: Annotated[str, "The email address of the user to be added as a project member."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-add-project-member'."]:
    """Add a user to an Enterprise Miro project.

    Use this tool to add a Miro user to a project under the Enterprise plan. Requires 'projects:write' scope and Company Admin role."""  # noqa: E501
    request_data = remove_none_values({"email": user_email_id, "role": project_member_role})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/members".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_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-miro"))
async def get_project_members(
    context: ToolContext,
    organization_id: Annotated[str, "The ID of the organization to which the project belongs."],
    project_identifier: Annotated[
        str, "The unique identifier of the project to retrieve its members."
    ],
    team_id: Annotated[str, "The unique ID of the team associated with the project."],
    maximum_results_per_call: Annotated[
        int | None,
        "Specifies the maximum number of project members to return per call. Additional results can be accessed via the cursor parameter.",  # noqa: E501
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "Indicator for the page position in the result set. Leave empty for the first page; use the prior response's cursor for next pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-project-members'."]:
    """Retrieve members of a specific project.

    Fetches the list of members involved in a specific project. Available only for Enterprise plan users with Company Admin role. Requires 'projects:read' scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/members".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_identifier
        ),
        method="GET",
        params=remove_none_values({"limit": maximum_results_per_call, "cursor": 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-miro"))
async def get_project_member_info(
    context: ToolContext,
    member_id: Annotated[str, "The ID of the project member to retrieve specific information."],
    organization_id: Annotated[str, "The ID of the organization to which the project belongs."],
    project_identifier: Annotated[
        str, "The ID of the project to get information about a specific member."
    ],
    team_identifier: Annotated[str, "The ID of the team to which the project belongs."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-project-member'."]:
    """Retrieve information about a specific project member.

    This tool retrieves detailed information for a specific project member within an organization. It should be called by users with Enterprise plan access and the role of Company Admin. The information is essential for managing project members and understanding their roles and contributions in a team environment."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id,
            team_id=team_identifier,
            project_id=project_identifier,
            member_id=member_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-miro"))
async def update_project_member_role(
    context: ToolContext,
    member_id: Annotated[
        str, "The unique identifier for the project member whose role you want to update."
    ],
    organization_id: Annotated[
        str, "The ID of the organization to which the project member belongs."
    ],
    project_id: Annotated[str, "The unique ID of the project to be updated."],
    team_id: Annotated[
        str,
        "The unique identifier for the team that the project member is associated with. Required for specifying which team the member belongs to.",  # noqa: E501
    ],
    project_member_role: Annotated[
        str | None,
        "The new role for the project member. Choose from 'owner', 'editor', 'viewer', 'commentator', or 'coowner'.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-update-project-member'."
]:
    """Update the role and details of a project member.

    This tool updates details of a project member, primarily focusing on the role within a project. It is specifically for Enterprise plan users with Company Admin rights. The required scope is 'projects:write'. This tool should be used to manage project membership roles and details efficiently."""  # noqa: E501
    request_data = remove_none_values({"role": project_member_role})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_id, project_id=project_id, member_id=member_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def remove_project_member(
    context: ToolContext,
    member_id: Annotated[str, "The ID of the member to be removed from the project."],
    organization_id: Annotated[str, "The ID of the organization to which the project belongs."],
    project_identifier: Annotated[
        str,
        "The unique identifier for the project from which to remove a member. This ID is required to specify the project accurately.",  # noqa: E501
    ],
    team_id: Annotated[
        str,
        "The ID of the team to which the project belongs, required for specifying the project context.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-delete-project-member'."
]:
    """Remove a member from a Miro project.

    This tool removes a specified member from a project within Miro. The user remains part of the team despite being removed from the project. This action is exclusive to Enterprise plan users with Company Admin roles."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/projects/{project_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id,
            team_id=team_id,
            project_id=project_identifier,
            member_id=member_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-miro"))
async def create_enterprise_team(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the organization where the team is being created."
    ],
    team_name: Annotated[
        str | None, "The name of the team to be created within the organization."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-create-team'."]:
    """Creates a new team in an existing Miro organization.

    This tool is used to create a new team within an existing organization in Miro. It is specifically available for Enterprise plan users who are Company Admins. This tool requires the 'organizations:teams:write' scope."""  # noqa: E501
    request_data = remove_none_values({"name": team_name})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams".format(org_id=organization_id),  # noqa: UP032
        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-miro"))
async def get_enterprise_teams(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier of the organization to retrieve teams from."
    ],
    maximum_results_limit: Annotated[
        int | None,
        "The maximum number of team records to retrieve. If not set, defaults to the API's default value.",  # noqa: E501
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "Indicator for page position in results. Leave empty for first page; use previous cursor for next pages.",  # noqa: E501
    ] = None,
    team_name_filter: Annotated[
        str | None,
        "Filters teams by name using case insensitive partial match. For example, 'dev' will return both 'Developer's team' and 'Team for developers'.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-teams'."]:
    """Retrieve list of teams in an enterprise organization.

    This tool is used to obtain a list of teams for a given organization in the Enterprise plan of Miro. It requires the user to have the role of a Company Admin and proper authorization scopes. It is essential for enterprises needing to manage or view their organizational structure."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams".format(org_id=organization_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_limit,
            "cursor": pagination_cursor,
            "name": team_name_filter,
        }),
        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-miro"))
async def get_team_info(
    context: ToolContext,
    organization_id: Annotated[str, "The unique identifier for the organization in Miro."],
    team_id: Annotated[
        str,
        "The unique identifier of the team to retrieve information for. This is required to specify which team within the organization you're inquiring about.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-team'."]:
    """Retrieve information about an existing team within an organization.

    This tool retrieves information about a specific team for users with Enterprise plan access on Miro. It requires the 'organizations:teams:read' scope and is available only to Company Admins. It should be called when detailed information about a team is needed."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_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-miro"))
async def update_enterprise_team(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier of the organization to which the team belongs."
    ],
    team_identifier: Annotated[str, "The unique identifier for the team to be updated."],
    new_team_name: Annotated[str | None, "Specify the new name for the team."] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-update-team'."]:
    """Update details of an existing enterprise team.

    This tool updates the details of an existing team in an organization for Enterprise plan users. It requires you to have the role of a Company Admin and the appropriate scope (organizations:teams:write)."""  # noqa: E501
    request_data = remove_none_values({"name": new_team_name})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def delete_enterprise_team(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique identifier for the organization. Required to specify which organization's team is to be deleted.",  # noqa: E501
    ],
    team_id: Annotated[
        str, "The unique identifier for the team to be deleted within the organization."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-delete-team'."]:
    """Deletes an existing team for enterprise users.

    This tool deletes a specific team within an enterprise organization, available only to users with Enterprise plans and a Company Admin role. It should be used when there's a need to remove an entire team from an organization's Miro account."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_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-miro"))
async def invite_miro_team_member(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the Miro organization to which the user belongs."
    ],
    team_identifier: Annotated[
        str, "Specify the unique identifier of the team within the organization."
    ],
    user_email_for_team_invitation: Annotated[
        str,
        "Email address of the user to be invited to the Miro team. Ensure the email belongs to a user in your Miro organization.",  # noqa: E501
    ],
    team_member_role: Annotated[
        str | None,
        "Specify the role for the team member: 'member', 'admin', or 'team_guest'. Determines access and permissions in the team.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-invite-team-member'."]:
    """Invite a new user to a Miro team within your organization.

    Use this tool to invite an existing Miro organization member to an existing team. Suitable for Enterprise plan users with Company Admin roles. Ensure compliance with required authorization scopes and rate limiting."""  # noqa: E501
    request_data = remove_none_values({
        "email": user_email_for_team_invitation,
        "role": team_member_role,
    })
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        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-miro"))
async def get_enterprise_team_members(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the organization whose team members are being retrieved."
    ],
    team_identifier: Annotated[
        str, "The unique identifier for the team whose members are being retrieved."
    ],
    filter_by_role: Annotated[
        str | None,
        "Filter team members by their role. Accepted values: 'member', 'admin', 'non_team', 'team_guest'.",  # noqa: E501
    ] = None,
    member_retrieval_limit: Annotated[
        int | None,
        "The maximum number of team members to retrieve in one call. Leave empty for default.",
    ] = 100,
    pagination_cursor: Annotated[
        str | None,
        "Indicates the page position for fetching results. Leave empty for the first page or use the value from the previous cursor field for subsequent pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-team-members'."]:
    """Retrieve team members for an enterprise organization team.

    This tool retrieves the list of team members for a specific team within an enterprise organization on Miro. It should be used when needing to access the team member details for organizational purposes. This tool is only available to users with an Enterprise plan and the role of Company Admin."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        method="GET",
        params=remove_none_values({
            "limit": member_retrieval_limit,
            "cursor": pagination_cursor,
            "role": filter_by_role,
        }),
        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-miro"))
async def retrieve_team_member_by_id(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier for the organization. Required to retrieve team member details."
    ],
    team_identifier: Annotated[str, "The unique identifier for the team within the organization."],
    team_member_id: Annotated[str, "The unique identifier for the team member to be retrieved."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-team-member'."]:
    """Retrieve team member details by ID for enterprise users.

    This tool fetches details about a specific team member within an organization using their ID. It is available only to Miro Enterprise plan users who are Company Admins, and requires the 'organizations:teams:read' scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier, member_id=team_member_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-miro"))
async def update_team_member_role(
    context: ToolContext,
    organization_id: Annotated[str, "The unique identifier for the organization."],
    team_identifier: Annotated[
        str, "The unique identifier for the team whose member's role is to be updated."
    ],
    team_member_id: Annotated[
        str, "The unique identifier of the team member whose role is to be updated."
    ],
    team_member_role: Annotated[
        str | None,
        "Role of the team member. Options: 'member', 'admin', 'team_guest'. Specifies permissions within the team.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-update-team-member'."]:
    """Update a team member's role in an enterprise team.

    This tool updates the role of a specified team member within an enterprise team. It is intended for use by Company Admins on the Miro Enterprise plan. Ensure you have the required scope 'organizations:teams:write' to utilize this endpoint."""  # noqa: E501
    request_data = remove_none_values({"role": team_member_role})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier, member_id=team_member_id
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-miro"))
async def remove_team_member(
    context: ToolContext,
    organization_id: Annotated[
        str, "The unique identifier of the organization from which a team member will be removed."
    ],
    team_identifier: Annotated[
        str,
        "The unique identifier of the team from which the member will be removed. This should be a string representing the Team ID.",  # noqa: E501
    ],
    team_member_id: Annotated[str, "The unique identifier of the team member to be removed."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-delete-team-member'."]:
    """Remove a team member from a team by ID within an enterprise.

    This tool deletes a team member from a specified team using their ID within an enterprise, requiring admin permissions and appropriate API access scope."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members/{member_id}".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier, member_id=team_member_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-miro"))
async def get_default_team_settings(
    context: ToolContext,
    organization_id: Annotated[
        str,
        "The unique identifier for an Organization within Miro's Enterprise plan. Required for retrieving default team settings.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'enterprise-get-default-team-settings'."
]:
    """Retrieve default team settings for an organization.

    This tool retrieves the default team settings for an existing organization within the Enterprise plan on Miro. It requires 'organizations:teams:read' scope and is accessible only to Company Admins."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/default_teams_settings".format(  # noqa: UP032
            org_id=organization_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-miro"))
async def get_team_settings(
    context: ToolContext,
    organization_id: Annotated[str, "The unique identifier for the organization."],
    team_id: Annotated[
        str, "The unique identifier for the team whose settings you want to retrieve."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'enterprise-get-team-settings'."]:
    """Fetches settings for a specific team in an organization.

    This tool retrieves team settings for a specified team within an organization in Miro. It is applicable only to users with the Enterprise plan who have the role of a Company Admin. Use this when you need detailed configuration information about a team."""  # noqa: E501
    request_data = remove_none_values({})
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_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-miro"))
async def update_team_settings(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    organization_id: Annotated[
        str | None,
        "The unique identifier for the organization to which the team belongs.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    team_identifier: Annotated[
        str | None,
        "The unique identifier of the team to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = 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 'enterprise-update-team-settings'."]:
    """Update settings for an existing team in Miro Enterprise.

    This tool updates the settings of a specified team within an organization using Miro's Enterprise API. It is intended for users with the role of Company Admin under the Enterprise plan. Ensure the required scopes are available for successful operation.

    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.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATETEAMSETTINGS"],
            "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 organization_id:
        missing_params.append(("organization_id", "path"))
    if not team_identifier:
        missing_params.append(("team_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["UPDATETEAMSETTINGS"] + "\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["UPDATETEAMSETTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/settings".format(  # noqa: UP032
            org_id=organization_id, team_id=team_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATETEAMSETTINGS"],
        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}
