"""Arcade Starter Tools for Figma

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,
    data: 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,
                method=method,
                params=params,
                headers=headers,
                data=data,  # type: ignore[arg-type]
            )
            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


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-figma", scopes=["file_content:read"]))
async def fetch_figma_file(
    context: ToolContext,
    file_key: Annotated[str, "The unique key of the Figma file or branch to retrieve as JSON."],
    version_id: Annotated[
        str | None,
        "Specify the version ID to retrieve a specific version of the file. Default is the current version.",  # noqa: E501
    ] = None,
    node_ids_of_interest: Annotated[
        str | None,
        "Comma-separated list of node IDs to retrieve specific parts of the Figma document.",
    ] = None,
    traversal_depth: Annotated[
        float | None,
        "Positive integer indicating the depth in the document tree to retrieve. For example, 1 returns only Pages; 2 returns Pages and top-level objects.",  # noqa: E501
    ] = None,
    export_vector_data: Annotated[
        str | None, 'Set to "paths" to include vector data in the response.'
    ] = None,
    include_plugin_data: Annotated[
        str | None,
        "Comma separated list of plugin IDs and/or 'shared'. Includes plugin data in the result.",
    ] = None,
    include_branch_metadata: Annotated[
        bool | None,
        "Set to true to include metadata about branches related to the file. If false, branch information will not be returned.",  # noqa: E501
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFile'."]:
    """Retrieve a Figma file as a JSON object using its file key.

    Use this tool to fetch a Figma document as JSON, identified by a file key. The returned data includes the document structure and metadata about components."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}".format(file_key=file_key),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "version": version_id,
            "ids": node_ids_of_interest,
            "depth": traversal_depth,
            "geometry": export_vector_data,
            "plugin_data": include_plugin_data,
            "branch_data": include_branch_metadata,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_content:read"]))
async def get_figma_file_nodes(
    context: ToolContext,
    node_ids_to_retrieve: Annotated[
        str, "A comma-separated list of Figma node IDs to retrieve as JSON."
    ],
    figma_file_key: Annotated[
        str, "The file or branch key from which to export JSON data in Figma."
    ],
    specific_version_id: Annotated[
        str | None,
        "Specify a version ID to retrieve a particular version of the Figma file. If omitted, the current version is retrieved.",  # noqa: E501
    ] = None,
    node_tree_depth: Annotated[
        float | None,
        "Positive integer indicating how deep into the node tree to traverse from the starting node. A value of 1 returns only immediate children. Leaving it unset returns all nodes.",  # noqa: E501
    ] = None,
    export_vector_data: Annotated[
        str | None, 'Set to "paths" to include vector data in the response.'
    ] = None,
    include_plugin_data: Annotated[
        str | None,
        "Comma-separated plugin IDs and/or 'shared' to include plugin-related data in results.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileNodes'."]:
    """Retrieve nodes and metadata from a Figma file.

    Use this tool to get detailed information about specific nodes in a Figma file, including metadata like name, last modified date, thumbnail URL, editor type, version, and link access permissions. Also retrieves document structure, component mappings, and styles. Useful for accessing and analyzing Figma design elements."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/nodes".format(file_key=figma_file_key),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "ids": node_ids_to_retrieve,
            "version": specific_version_id,
            "depth": node_tree_depth,
            "geometry": export_vector_data,
            "plugin_data": include_plugin_data,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_content:read"]))
async def render_figma_images(
    context: ToolContext,
    node_ids_to_render: Annotated[
        str, "A comma-separated list of node IDs for images to be rendered."
    ],
    figma_file_key: Annotated[
        str,
        "The key for the Figma file or branch to export images from. Use with the `branch_data` query parameter to obtain branch key if needed.",  # noqa: E501
    ],
    version_id: Annotated[
        str | None,
        "Specify a version ID to retrieve a particular version of a Figma file. If omitted, will use the current version.",  # noqa: E501
    ] = None,
    image_scale_factor: Annotated[
        float | None,
        "A number between 0.01 and 4, representing the image scaling factor for rendering.",
    ] = None,
    image_output_format: Annotated[
        str | None,
        "Specify the image format for the output. Options are 'jpg', 'png', 'svg', or 'pdf'.",
    ] = "png",
    render_text_as_outlines: Annotated[
        bool | None,
        "Determines if text elements are rendered as outlines in SVGs. Set `true` for visual consistency; `false` for selectable text.",  # noqa: E501
    ] = True,
    include_svg_id_attributes: Annotated[
        bool | None,
        "Include id attributes for all SVG elements. Adds the layer name to the 'id' attribute.",
    ] = False,
    include_node_id_in_svg_elements: Annotated[
        bool | None,
        "Set to true to include node ID attributes for all SVG elements, adding the node ID to a `data-node-id` attribute.",  # noqa: E501
    ] = False,
    svg_stroke_simplification_enabled: Annotated[
        bool | None,
        "Set to true to simplify inside/outside strokes in SVG using stroke attributes instead of `<mask>`.",  # noqa: E501
    ] = True,
    exclude_overlapping_content: Annotated[
        bool | None,
        "Set to true to exclude overlapping content from rendering. Set to false to include overlaps, which may increase processing time.",  # noqa: E501
    ] = True,
    use_full_dimensions_without_cropping: Annotated[
        bool | None,
        "Export using full node dimensions, ignoring cropping and empty space. Ensures text nodes are fully visible.",  # noqa: E501
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getImages'."]:
    """Fetch rendered images from Figma files by node IDs.

    Use this tool to render and retrieve images from a Figma file by specifying node IDs. The tool returns a mapping of node IDs to URLs of the rendered images. Note: Some entries may be null if rendering fails for certain nodes. Images expire after 30 days."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/images/{file_key}".format(file_key=figma_file_key),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "ids": node_ids_to_render,
            "version": version_id,
            "scale": image_scale_factor,
            "format": image_output_format,
            "svg_outline_text": render_text_as_outlines,
            "svg_include_id": include_svg_id_attributes,
            "svg_include_node_id": include_node_id_in_svg_elements,
            "svg_simplify_stroke": svg_stroke_simplification_enabled,
            "contents_only": exclude_overlapping_content,
            "use_absolute_bounds": use_full_dimensions_without_cropping,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_content:read"]))
async def fetch_image_fill_links(
    context: ToolContext,
    file_or_branch_key: Annotated[
        str,
        "The file or branch key from which to retrieve image URLs. Use `GET /v1/files/:key` to get the branch key if needed.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getImageFills'."]:
    """Retrieve download links for images in a Figma document.

    Use this tool to get download links for all images present in the image fills of a Figma document. These links allow access to images users have added to their design files. The URLs will expire after about 14 days and can be found using image references."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/images".format(file_key=file_or_branch_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_metadata:read"]))
async def get_file_metadata(
    context: ToolContext,
    file_identifier: Annotated[
        str,
        "File or branch key to get metadata for. Use the `branch_data` query param to get the branch key.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileMeta'."]:
    """Retrieve metadata for a specified Figma file.

    This tool is used to obtain metadata information from a specific Figma file by providing the file key. It can be called when users need to access details about a Figma file, such as its name, creator, and other properties."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/meta".format(file_key=file_identifier),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["projects:read"]))
async def figma_get_team_projects(
    context: ToolContext,
    team_id: Annotated[
        str,
        "The unique ID of the Figma team to list projects from. This is required to specify which team's projects to retrieve.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getTeamProjects'."]:
    """Fetch all projects within a specified Figma team.

    This tool retrieves a list of projects for a specified team in Figma, visible to the authenticated user."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/teams/{team_id}/projects".format(team_id=team_id),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["projects:read"]))
async def get_figma_project_files(
    context: ToolContext,
    project_identifier: Annotated[
        str, "The unique string ID of the Figma project from which to list files."
    ],
    include_branch_metadata: Annotated[
        bool | None,
        "Include branch metadata for each main file with a branch in the project. Set to true to receive this data, otherwise false.",  # noqa: E501
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getProjectFiles'."]:
    """Retrieve all files from a specific Figma project.

    This tool fetches a list of all files within the specified Figma project. Use it when you need to access or manage project files stored in Figma."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/projects/{project_id}/files".format(  # noqa: UP032
            project_id=project_identifier
        ),
        method="GET",
        params=remove_none_values({"branch_data": include_branch_metadata}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_versions:read"]))
async def fetch_file_version_history(
    context: ToolContext,
    target_file_key: Annotated[
        str,
        "The key of the file or branch to fetch version history for. Use this to specify the Figma file whose version history you need.",  # noqa: E501
    ],
    number_of_items_per_page: Annotated[
        float | None,
        "Specify the number of items to return per page. Defaults to 30 if not provided.",
    ] = None,
    get_versions_before_id: Annotated[
        float | None, "A version ID to get versions before it in the history. Used for pagination."
    ] = None,
    after_version_id: Annotated[
        float | None,
        "Version ID to fetch subsequent versions. Used for pagination. Omit if not paginating.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileVersions'."]:
    """Fetch the version history of a Figma file.

    Use this tool to obtain the version history of a specific Figma file, enabling the analysis of its changes over time. This can be useful for reviewing past edits or rendering specific versions."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/versions".format(file_key=target_file_key),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "page_size": number_of_items_per_page,
            "before": get_versions_before_id,
            "after": after_version_id,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:read"]))
async def get_figma_file_comments(
    context: ToolContext,
    figma_file_or_branch_key: Annotated[
        str,
        "Specify the file or branch key to retrieve comments from. Use the `GET /v1/files/:key` endpoint with `branch_data` query param for branch keys.",  # noqa: E501
    ],
    return_comments_as_markdown: Annotated[
        bool | None, "Set to true to return comments as markdown equivalents when applicable."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getComments'."]:
    """Retrieve comments from a Figma file.

    Use this tool to get a list of comments left on a specific Figma file. It is useful for accessing feedback or discussions related to file elements."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments".format(  # noqa: UP032
            file_key=figma_file_or_branch_key
        ),
        method="GET",
        params=remove_none_values({"as_md": return_comments_as_markdown}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:write"]))
async def add_comment_to_figma_file(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    figma_file_key: Annotated[
        str | None,
        "File or branch key for the Figma file where the comment will be added. Retrieve this using `GET /v1/files/:key` with the `branch_data` query param for branch keys.  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 'postComment'."]:
    """Posts a new comment on a Figma file.

    Use this tool to post a new comment on a specified file in Figma, identified by file_key.

    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[
                "ADDCOMMENTTOFIGMAFILE_REQUEST_BODY_SCHEMA"
            ],
            "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 path parameter: figma_file_key
    if not figma_file_key:
        raise RetryableToolError(
            message="Invalid or missing figma_file_key parameter",
            developer_message=f"Path parameter "
            f"'figma_file_key' validation failed. "
            f"Received: {figma_file_key}",
            additional_prompt_content=(
                "The 'figma_file_key' parameter is required when "
                "executing this operation. "
                "Please call this tool again with a valid "
                "figma_file_key value."
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["ADDCOMMENTTOFIGMAFILE_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["ADDCOMMENTTOFIGMAFILE_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["ADDCOMMENTTOFIGMAFILE_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['ADDCOMMENTTOFIGMAFILE_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments".format(file_key=figma_file_key),  # 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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:write"]))
async def delete_figma_comment(
    context: ToolContext,
    figma_file_key: Annotated[
        str,
        "The file or branch key from which to delete the comment. Use `GET /v1/files/:key` with `branch_data` to obtain the branch key.",  # noqa: E501
    ],
    comment_identifier: Annotated[
        str,
        "The ID of the comment you wish to delete from the Figma file. Only the original commenter can perform this action.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'deleteComment'."]:
    """Delete your comment from a Figma file.

    Use this tool to delete a specific comment you made on a Figma file. Only the original commenter can delete their comments."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments/{comment_id}".format(  # noqa: UP032
            file_key=figma_file_key, comment_id=comment_identifier
        ),
        method="DELETE",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:read"]))
async def fetch_comment_reactions(
    context: ToolContext,
    file_or_branch_key: Annotated[
        str, "The key for the file or branch to retrieve the comment reactions from in Figma."
    ],
    comment_id: Annotated[str, "ID of the comment from which to retrieve reactions."],
    pagination_cursor: Annotated[
        str | None,
        "Cursor for pagination. Use the cursor from the previous call's response to retrieve the next set of reactions.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getCommentReactions'."]:
    """Retrieve reactions from a specific comment in Figma.

    Use this tool to obtain a list of reactions left on a specific comment in a Figma file. This can help track engagement or feedback on comments."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments/{comment_id}/reactions".format(  # noqa: UP032
            file_key=file_or_branch_key, comment_id=comment_id
        ),
        method="GET",
        params=remove_none_values({"cursor": pagination_cursor}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:write"]))
async def add_figma_comment_reaction(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    file_or_branch_key: Annotated[
        str | None,
        "Key of the file or branch where the comment reaction should be posted. Can be obtained via the Figma API.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    comment_id: Annotated[
        str | None,
        "The unique identifier of the comment you want to react to in a Figma file.  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 'postCommentReaction'."]:
    """Add a reaction to a comment on a Figma file.

    Use this tool to post a new reaction to an existing comment on a Figma file. It should be called when you want to react to a specific comment in a Figma project.

    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[
                "ADDFIGMACOMMENTREACTION_REQUEST_BODY_SCHEMA"
            ],
            "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 path parameter: file_or_branch_key
    if not file_or_branch_key:
        raise RetryableToolError(
            message="Invalid or missing file_or_branch_key parameter",
            developer_message=f"Path parameter "
            f"'file_or_branch_key' validation failed. "
            f"Received: {file_or_branch_key}",
            additional_prompt_content=(
                "The 'file_or_branch_key' parameter is required when "
                "executing this operation. "
                "Please call this tool again with a valid "
                "file_or_branch_key value."
            ),
        )

    # Validate required path parameter: comment_id
    if not comment_id:
        raise RetryableToolError(
            message="Invalid or missing comment_id parameter",
            developer_message=f"Path parameter "
            f"'comment_id' validation failed. "
            f"Received: {comment_id}",
            additional_prompt_content=(
                "The 'comment_id' parameter is required when "
                "executing this operation. "
                "Please call this tool again with a valid "
                "comment_id value."
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["ADDFIGMACOMMENTREACTION_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["ADDFIGMACOMMENTREACTION_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["ADDFIGMACOMMENTREACTION_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['ADDFIGMACOMMENTREACTION_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments/{comment_id}/reactions".format(  # noqa: UP032
            file_key=file_or_branch_key, comment_id=comment_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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_comments:write"]))
async def delete_my_comment_reaction(
    context: ToolContext,
    reaction_emoji: Annotated[
        str,
        "The emoji associated with the reaction to be deleted. Only the emoji used for the reaction you added can be deleted.",  # noqa: E501
    ],
    file_or_branch_key: Annotated[
        str,
        "Key of the Figma file or branch where the reaction should be deleted. Use `GET /v1/files/:key` with the `branch_data` query param to obtain the branch key if needed.",  # noqa: E501
    ],
    comment_id: Annotated[
        str, "The ID of the comment from which you want to delete your reaction."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'deleteCommentReaction'."]:
    """Deletes your specific comment reaction in Figma.

    This tool allows users to delete a reaction they added to a comment in a Figma file. It can only be used if the reaction was made by the person attempting to delete it."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/comments/{comment_id}/reactions".format(  # noqa: UP032
            file_key=file_or_branch_key, comment_id=comment_id
        ),
        method="DELETE",
        params=remove_none_values({"emoji": reaction_emoji}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["current_user:read"]))
async def get_user_information(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getMe'."]:
    """Retrieve information for the authenticated Figma user.

    Use this tool to obtain the profile information of the user currently authenticated in the Figma service."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/me",
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["team_library_content:read"]))
async def get_team_components(
    context: ToolContext,
    team_id: Annotated[
        str,
        "The unique identifier of the team whose components you want to retrieve. This ID is necessary to specify the source team library in Figma.",  # noqa: E501
    ],
    number_of_items_per_page: Annotated[
        float | None,
        "Specify the number of components to return in one page. Defaults to 30, maximum is 1000.",
    ] = 30,
    cursor_after_id: Annotated[
        float | None,
        "Cursor indicating which ID to start retrieving components after. Cannot be used with 'before'.",  # noqa: E501
    ] = None,
    cursor_before: Annotated[
        float | None,
        "Cursor to retrieve components starting before a specific id. Exclusive with 'cursor_after'.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getTeamComponents'."]:
    """Retrieve published components from a team's Figma library.

    Use this tool to get a list of components that have been published within a specified team's library in Figma."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/teams/{team_id}/components".format(team_id=team_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "page_size": number_of_items_per_page,
            "after": cursor_after_id,
            "before": cursor_before,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_content:read"]))
async def get_figma_file_components(
    context: ToolContext,
    file_key: Annotated[str, "Main file key to list components from. Must not be a branch key."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileComponents'."]:
    """Retrieve published components from a Figma file library.

    Use this tool to obtain a list of all published components within a specific Figma file, allowing for design asset management or analysis."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/components".format(file_key=file_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_assets:read"]))
async def get_figma_component_metadata(
    context: ToolContext,
    component_key: Annotated[
        str, "The unique identifier of the Figma component to retrieve metadata for."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getComponent'."]:
    """Retrieve metadata for a Figma component by key.

    Use this tool to obtain detailed metadata about a specific Figma component using its key."""
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/components/{key}".format(key=component_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["team_library_content:read"]))
async def get_team_component_sets(
    context: ToolContext,
    team_id: Annotated[
        str, "The unique identifier for the team from which to list component sets."
    ],
    number_of_items_per_page: Annotated[
        float | None,
        "Specify the number of items to return per page in the results. Defaults to 30.",
    ] = 30,
    start_after_cursor: Annotated[
        float | None,
        "Cursor indicating the starting point for retrieving component sets, exclusive with `end_before_cursor`. This cursor is an internally tracked integer not corresponding to any IDs.",  # noqa: E501
    ] = None,
    cursor_before_id: Annotated[
        float | None,
        "Cursor ID indicating the point before which to retrieve component sets. It must be exclusive with the 'after' cursor.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getTeamComponentSets'."]:
    """Fetch published component sets from a Figma team library.

    This tool retrieves a paginated list of component sets that have been published within a specified team library in Figma. It should be used when you need to access or explore available component sets in a particular Figma team."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/teams/{team_id}/component_sets".format(team_id=team_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "page_size": number_of_items_per_page,
            "after": start_after_cursor,
            "before": cursor_before_id,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_content:read"]))
async def get_published_component_sets(
    context: ToolContext,
    main_file_key: Annotated[
        str,
        "The main file key of the Figma file to list component sets from. Must not be a branch key.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileComponentSets'."]:
    """Retrieve published component sets from a Figma file.

    Call this tool to get a list of published component sets within a specified Figma file library. Useful for accessing design components efficiently."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/component_sets".format(  # noqa: UP032
            file_key=main_file_key
        ),
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_assets:read"]))
async def get_figma_component_set(
    context: ToolContext,
    component_set_key: Annotated[
        str, "The unique key identifier for the Figma component set to retrieve metadata."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getComponentSet'."]:
    """Retrieve metadata for a Figma component set using its key.

    Use this tool to obtain detailed metadata about a published component set in Figma by providing its unique key identifier."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/component_sets/{key}".format(key=component_set_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["team_library_content:read"]))
async def get_team_styles(
    context: ToolContext,
    team_id: Annotated[
        str, "The unique identifier of the team from which to retrieve published styles."
    ],
    items_per_page: Annotated[
        float | None,
        "Specify the number of styles to return per page. Defaults to 30 if not provided.",
    ] = 30,
    start_after_cursor: Annotated[
        float | None,
        "Cursor to start retrieving styles after a specific ID. Cannot be used with before. Internally tracked integer.",  # noqa: E501
    ] = None,
    cursor_before_id: Annotated[
        float | None,
        "Cursor for retrieving styles before a specific ID. Use this to paginate backwards. Exclusive with after.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getTeamStyles'."]:
    """Retrieve a list of published styles from a team's library in Figma.

    This tool retrieves a paginated list of styles that have been published within a specified team's library in Figma. It should be called when you need access to design styles, such as colors or text styles, that a team has made available in their library."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/teams/{team_id}/styles".format(team_id=team_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "page_size": items_per_page,
            "after": start_after_cursor,
            "before": cursor_before_id,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_content:read"]))
async def get_published_styles_from_file(
    context: ToolContext,
    main_file_key: Annotated[str, "Main file key to list styles from. Must not be a branch key."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getFileStyles'."]:
    """Retrieve published styles from a Figma file library.

    Use this tool to get a list of published styles within a specific Figma file library. Useful when you need to analyze or display the styles used in a Figma file."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/styles".format(file_key=main_file_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_assets:read"]))
async def get_style_metadata(
    context: ToolContext,
    style_key: Annotated[str, "The unique identifier of the Figma style to retrieve metadata for."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getStyle'."]:
    """Retrieve Figma style metadata by key.

    Use this tool to get detailed metadata about a specific style in Figma using its unique key. Useful for accessing style information for design analysis or integration."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/styles/{key}".format(key=style_key),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:read"]))
async def get_figma_webhooks(
    context: ToolContext,
    webhook_context: Annotated[
        str | None, "Specify the context for the webhooks. Accepts 'team', 'project', or 'file'."
    ] = None,
    context_identifier: Annotated[
        str | None,
        "The ID of the context to fetch attached webhooks. Cannot be used with plan_api_id.",
    ] = None,
    plan_id_for_webhooks: Annotated[
        str | None,
        "The ID of your plan for retrieving webhooks across all accessible contexts. Cannot be used with context or context_id.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "Cursor for pagination when using plan_api_id. Provide next_page or prev_page from previous response to navigate pages.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getWebhooks'."]:
    """Retrieve a list of webhooks from Figma.

    Call this tool to get a list of webhooks available in your Figma context or plan. This can be used to manage and view all the webhooks you have access to, with results provided in a paginated format."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v2/webhooks",
        method="GET",
        params=remove_none_values({
            "context": webhook_context,
            "context_id": context_identifier,
            "plan_api_id": plan_id_for_webhooks,
            "cursor": pagination_cursor,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:write"]))
async def create_figma_webhook(
    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 'postWebhook'."]:
    """Create a new webhook for Figma events.

    This tool creates a webhook in Figma that triggers an event to a specified endpoint. It sends a PING event by default, unless set to PAUSED, and can be reactivated later.

    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["CREATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"],
            "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.dumps(
                    REQUEST_BODY_SCHEMAS["CREATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["CREATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["CREATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['CREATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v2/webhooks",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:read"]))
async def get_figma_webhook(
    context: ToolContext,
    webhook_id: Annotated[str, "Unique identifier of the Figma webhook to retrieve."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getWebhook'."]:
    """Retrieve a Figma webhook by its ID.

    Use this tool to obtain detailed information about a specific Figma webhook by providing its unique ID."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v2/webhooks/{webhook_id}".format(webhook_id=webhook_id),  # noqa: UP032
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:write"]))
async def update_figma_webhook(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    webhook_id_to_update: Annotated[
        str | None,
        "Provide the ID of the Figma webhook you want 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 'putWebhook'."]:
    """Update a Figma webhook by its ID.

    Use this tool to update the settings or parameters of an existing webhook in Figma by specifying the webhook ID.

    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["UPDATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"],
            "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 path parameter: webhook_id_to_update
    if not webhook_id_to_update:
        raise RetryableToolError(
            message="Invalid or missing webhook_id_to_update parameter",
            developer_message=f"Path parameter "
            f"'webhook_id_to_update' validation failed. "
            f"Received: {webhook_id_to_update}",
            additional_prompt_content=(
                "The 'webhook_id_to_update' parameter is required when "
                "executing this operation. "
                "Please call this tool again with a valid "
                "webhook_id_to_update value."
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["UPDATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["UPDATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["UPDATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['UPDATEFIGMAWEBHOOK_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v2/webhooks/{webhook_id}".format(  # noqa: UP032
            webhook_id=webhook_id_to_update
        ),
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:write"]))
async def delete_figma_webhook(
    context: ToolContext,
    webhook_id_to_delete: Annotated[
        str,
        "The unique identifier of the webhook you wish to delete. This ID is required for the deletion operation.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'deleteWebhook'."]:
    """Delete a specified webhook in Figma.

    Use this tool to delete a specified webhook in Figma. This action is irreversible and should be called when a webhook is no longer needed."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v2/webhooks/{webhook_id}".format(  # noqa: UP032
            webhook_id=webhook_id_to_delete
        ),
        method="DELETE",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["webhooks:read"]))
async def get_recent_webhook_requests(
    context: ToolContext,
    webhook_subscription_id: Annotated[
        str, "The ID of the webhook subscription for which to retrieve recent events."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getWebhookRequests'."]:
    """Retrieve recent webhook requests from the last week.

    Use this tool to gather webhook requests sent within the last week for debugging purposes."""
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v2/webhooks/{webhook_id}/requests".format(  # noqa: UP032
            webhook_id=webhook_subscription_id
        ),
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_variables:read"]))
async def retrieve_figma_local_variables(
    context: ToolContext,
    file_or_branch_key: Annotated[
        str,
        "The key for the file or branch to retrieve variables from in Figma. If a branch, use `GET /v1/files/:key` with the `branch_data` query param to get the branch key.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getLocalVariables'."]:
    """Retrieve local and remote variables from a Figma file.

    Use this tool to get a list of local variables created in a Figma file and any remote variables utilized, identified by their `subscribed_id`. This tool is available to full members of Enterprise organizations. It's useful for examining mode values and understanding variable usage within a file."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/variables/local".format(  # noqa: UP032
            file_key=file_or_branch_key
        ),
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_variables:read"]))
async def get_published_variables(
    context: ToolContext,
    main_file_key: Annotated[
        str,
        "The key of the Figma file to retrieve published variables from. Only use the main file key, not a branch key.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getPublishedVariables'."]:
    """Retrieve published variables from a Figma file.

    Call this tool to get a list of variables that are published from a specified Figma file. The response includes variable and collection details along with `subscribed_id`. Ideal for users needing information on published variables from a file within an Enterprise organization."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/variables/published".format(  # noqa: UP032
            file_key=main_file_key
        ),
        method="GET",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_variables:write"]))
async def manage_figma_variables(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    file_identifier: Annotated[
        str | None,
        "Specifies the Figma file or branch key to modify variables. Retrieve branch key using `GET /v1/files/:key` with `branch_data` parameter.  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 'postVariables'."]:
    """Manage and organize Figma variable collections in bulk.

    This tool allows you to create, update, and delete variable collections, modes, and variables within Figma files for Enterprise members with Editor seats. Use it to handle variable operations in bulk, including setting mode values and managing temporary IDs. Ideal for organizing complex Figma projects 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["MANAGEFIGMAVARIABLES_REQUEST_BODY_SCHEMA"],
            "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 path parameter: file_identifier
    if not file_identifier:
        raise RetryableToolError(
            message="Invalid or missing file_identifier parameter",
            developer_message=f"Path parameter "
            f"'file_identifier' validation failed. "
            f"Received: {file_identifier}",
            additional_prompt_content=(
                "The 'file_identifier' parameter is required when "
                "executing this operation. "
                "Please call this tool again with a valid "
                "file_identifier value."
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["MANAGEFIGMAVARIABLES_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["MANAGEFIGMAVARIABLES_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["MANAGEFIGMAVARIABLES_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['MANAGEFIGMAVARIABLES_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/variables".format(file_key=file_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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_dev_resources:read"]))
async def get_dev_resources(
    context: ToolContext,
    file_key: Annotated[
        str,
        "The main file key for fetching development resources from a Figma file. Ensure it is not a branch key.",  # noqa: E501
    ],
    target_node_ids: Annotated[
        str | None,
        "Comma separated list of node IDs to filter dev resources. If left blank, returns resources for all nodes.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getDevResources'."]:
    """Retrieve development resources from a Figma file.

    Use this tool to gather development resources from a specific Figma file using the file key."""
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/dev_resources".format(file_key=file_key),  # noqa: UP032
        method="GET",
        params=remove_none_values({"node_ids": target_node_ids}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_dev_resources:write"]))
async def create_bulk_dev_resources(
    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 'postDevResources'."]:
    """Bulk create developer resources in multiple Figma files.

    Use this tool to create multiple developer resources across different Figma files. Successfully created resources will be listed in the response, while any errors will also be provided, indicating issues such as non-existent file keys or duplicate URLs.

    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[
                "CREATEBULKDEVRESOURCES_REQUEST_BODY_SCHEMA"
            ],
            "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.dumps(
                    REQUEST_BODY_SCHEMAS["CREATEBULKDEVRESOURCES_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["CREATEBULKDEVRESOURCES_REQUEST_BODY_SCHEMA"], indent=2
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["CREATEBULKDEVRESOURCES_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['CREATEBULKDEVRESOURCES_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v1/dev_resources",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_dev_resources:write"]))
async def bulk_update_figma_dev_resources(
    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 'putDevResources'."]:
    """Update multiple Figma dev resources in bulk.

    This tool updates developer resources across multiple Figma files. It should be called when you need to apply changes to several resources at once. The response will include arrays indicating which resources were successfully updated and which encountered errors.

    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[
                "BULKUPDATEFIGMADEVRESOURCES_REQUEST_BODY_SCHEMA"
            ],
            "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.dumps(
                    REQUEST_BODY_SCHEMAS["BULKUPDATEFIGMADEVRESOURCES_REQUEST_BODY_SCHEMA"],
                    indent=2,
                )
            ),
        )

    # 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.dumps(
                    REQUEST_BODY_SCHEMAS["BULKUPDATEFIGMADEVRESOURCES_REQUEST_BODY_SCHEMA"],
                    indent=2,
                )
            ),
        ) from e

    # Validate against schema
    is_valid, validation_error = validate_json_against_schema(
        request_data, REQUEST_BODY_SCHEMAS["BULKUPDATEFIGMADEVRESOURCES_REQUEST_BODY_SCHEMA"]
    )
    if not is_valid:
        raise RetryableToolError(
            message=f"Request body validation failed: {validation_error}",
            developer_message=f"Schema validation error: {validation_error}",
            additional_prompt_content=(
                f"The request body does not match the required schema. "
                f"Errors:\n{validation_error}\n\n"
                "Please fix the validation errors above and call this tool "
                "again in execute mode with the corrected JSON.\n\n"
                "Required schema:\n\n"
                f"{json.dumps(REQUEST_BODY_SCHEMAS['BULKUPDATEFIGMADEVRESOURCES_REQUEST_BODY_SCHEMA'], indent=2)}"  # noqa: E501
            ),
        )

    # Make the actual API request
    response = await make_request(
        url="https://api.figma.com/v1/dev_resources",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["file_dev_resources:write"]))
async def delete_dev_resource(
    context: ToolContext,
    target_file_key: Annotated[
        str, "The main file key from which to delete the dev resource. Must not be a branch key."
    ],
    dev_resource_id: Annotated[
        str, "The ID of the developer resource to delete from the Figma file."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'deleteDevResource'."]:
    """Delete a dev resource from a Figma file.

    Call this tool to delete a specific developer resource from a Figma file using the file key and resource ID."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/files/{file_key}/dev_resources/{dev_resource_id}".format(  # noqa: UP032
            file_key=target_file_key, dev_resource_id=dev_resource_id
        ),
        method="DELETE",
        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()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def get_library_analytics_component_actions(
    context: ToolContext,
    group_by_dimension: Annotated[
        str,
        "Specify the dimension to group the analytics data by. Options are 'component' or 'team'.",
    ],
    library_file_key: Annotated[
        str, "The unique file key for the Figma library to retrieve analytics data from."
    ],
    data_page_cursor: Annotated[
        str | None,
        "Cursor indicating the specific page of data to fetch, obtained from a previous API call.",
    ] = None,
    earliest_start_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) for the earliest week to include. Rounded back to the start of a week. Defaults to one year prior.",  # noqa: E501
    ] = None,
    latest_inclusion_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) of the latest week to include, rounded forward to the nearest week's end. Defaults to the latest computed week.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsComponentActions'."
]:
    """Get analytics for library component actions.

    Retrieve detailed data on library component actions in Figma, broken down by the specified dimension. This tool is used to gain insights into how components in a Figma library are being utilized."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/component/actions".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({
            "cursor": data_page_cursor,
            "group_by": group_by_dimension,
            "start_date": earliest_start_date,
            "end_date": latest_inclusion_date,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def fetch_component_usage_data(
    context: ToolContext,
    group_by_dimension: Annotated[
        str,
        "A dimension to group the returned analytics data. Choose between 'component' or 'file'.",
    ],
    library_file_key: Annotated[
        str,
        "The file key of the library to fetch analytics data for. Required for specifying the target library.",  # noqa: E501
    ],
    data_page_cursor: Annotated[
        str | None, "Cursor indicating which page of data to fetch, obtained from a prior API call."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsComponentUsages'."
]:
    """Fetch library analytics component usage data by dimension.

    This tool retrieves a list of library analytics component usage data, providing insights into how components are used, broken down by the specified dimension."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/component/usages".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({"cursor": data_page_cursor, "group_by": group_by_dimension}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def get_library_style_actions(
    context: ToolContext,
    group_by_dimension: Annotated[
        str, "Specify the dimension ('style' or 'team') to group the returned analytics data by."
    ],
    library_file_key: Annotated[
        str, "The unique file key of the Figma library to retrieve analytics data for."
    ],
    pagination_cursor: Annotated[
        str | None,
        "A cursor to indicate which page of data to fetch. Obtain this from a prior API call.",
    ] = None,
    earliest_week_start_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) for the earliest week to include. Dates round back to the nearest week start. Defaults to one year prior.",  # noqa: E501
    ] = None,
    end_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) for the latest week to include, rounded to the week's end. Defaults to the latest computed week if not specified.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsStyleActions'."]:
    """Retrieve library style analytics actions data by dimension.

    Use this tool to obtain detailed actions data for styles in a Figma library, categorized by the specified dimension. Ideal for analyzing how styles are used or modified."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/style/actions".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({
            "cursor": pagination_cursor,
            "group_by": group_by_dimension,
            "start_date": earliest_week_start_date,
            "end_date": end_date,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def get_library_style_usage_data(
    context: ToolContext,
    group_by_dimension: Annotated[
        str, "Dimension to group the returned analytics data by. Options are 'style' or 'file'."
    ],
    library_file_key: Annotated[
        str,
        "The file key of the Figma library to fetch analytics data for. This is required to specify the source library.",  # noqa: E501
    ],
    pagination_cursor: Annotated[
        str | None,
        "Cursor indicating which page of data to fetch, obtained from a previous API call.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsStyleUsages'."]:
    """Retrieve style usage data from Figma library analytics.

    This tool returns library analytics style usage data from Figma, broken down by the requested dimension. It should be used to analyze style usage within a specific Figma library file."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/style/usages".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({"cursor": pagination_cursor, "group_by": group_by_dimension}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def fetch_library_analytics_variable_actions(
    context: ToolContext,
    group_by_dimension: Annotated[
        str, "A dimension to group the returned analytics data by. Options: 'variable', 'team'."
    ],
    library_file_key: Annotated[
        str, "The file key of the library for which to fetch analytics data."
    ],
    page_cursor: Annotated[
        str | None,
        "Cursor to indicate which page of data to fetch, obtained from a previous API call.",
    ] = None,
    earliest_week_start_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) representing the earliest week to include. Rounded back to the nearest week's start. Defaults to one year prior.",  # noqa: E501
    ] = None,
    end_date: Annotated[
        str | None,
        "ISO 8601 date string (YYYY-MM-DD) for the latest week to include. Defaults to the latest computed week.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsVariableActions'."
]:
    """Retrieve library analytics variable actions data from Figma.

    Call this tool to obtain a breakdown of library analytics variable actions data from Figma based on a specific dimension."""  # noqa: E501
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/variable/actions".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({
            "cursor": page_cursor,
            "group_by": group_by_dimension,
            "start_date": earliest_week_start_date,
            "end_date": end_date,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-figma", scopes=["library_analytics:read"]))
async def get_library_analytics_variable_usages(
    context: ToolContext,
    group_by_dimension: Annotated[
        str, "Specifies the dimension ('variable' or 'file') for grouping library analytics data."
    ],
    library_file_key: Annotated[str, "The unique key of the library to fetch analytics data from."],
    page_cursor: Annotated[
        str | None,
        "A token to fetch the specific page of results, received from a previous API call.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'getLibraryAnalyticsVariableUsages'."
]:
    """Retrieve analytics on library variable usage.

    Fetches a breakdown of library analytics variable usage data by the specified dimension."""
    request_data = remove_none_values({})
    response = await make_request(
        url="https://api.figma.com/v1/analytics/libraries/{file_key}/variable/usages".format(  # noqa: UP032
            file_key=library_file_key
        ),
        method="GET",
        params=remove_none_values({"cursor": page_cursor, "group_by": group_by_dimension}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        data=json.dumps(request_data) if request_data else None,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}
