from typing import Any, Optional

from httpx import AsyncClient
from langchain_core.messages import HumanMessage
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
from openai.types.responses import ResponseFunctionWebSearch, ResponseReasoningItem
from openai.types.responses.response_function_web_search import (
    ActionFind,
    ActionOpenPage,
    ActionSearch,
)
from openai.types.responses.response_output_message import ResponseOutputMessage
from openai.types.responses.response_output_refusal import ResponseOutputRefusal
from openai.types.responses.response_output_text import (
    Annotation,
    AnnotationURLCitation,
)
from pydantic import BaseModel, ConfigDict, Field
from typing_extensions import override
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
from unique_toolkit.agentic.short_term_memory_manager.persistent_short_term_memory_manager import (
    PersistentShortMemoryManager,
)
from unique_toolkit.agentic.tools.agent_chunks_hanlder import AgentChunksHandler
from unique_toolkit.agentic.tools.factory import ToolFactory
from unique_toolkit.agentic.tools.schemas import ToolCallResponse
from unique_toolkit.agentic.tools.tool import Tool
from unique_toolkit.app.schemas import ChatEvent
from unique_toolkit.chat.schemas import (
    MessageExecutionType,
    MessageExecutionUpdateStatus,
    MessageLogDetails,
    MessageLogEvent,
    MessageLogStatus,
    MessageLogUncitedReferences,
)
from unique_toolkit.chat.service import LanguageModelToolDescription
from unique_toolkit.content.schemas import ContentReference
from unique_toolkit.content.service import ContentService
from unique_toolkit.framework_utilities.openai.client import get_async_openai_client
from unique_toolkit.language_model.schemas import (
    LanguageModelFunction,
    LanguageModelMessage,
    LanguageModelMessageRole,
)
from unique_toolkit.short_term_memory.service import ShortTermMemoryService

from unique_deep_research.unique_custom.tools import crawl_url

from .config import (
    RESPONSES_API_TIMEOUT_SECONDS,
    TEMPLATE_ENV,
    DeepResearchEngine,
    DeepResearchToolConfig,
    UniqueEngine,
)
from .markdown_utils import (
    postprocess_research_result_with_chunks,
    validate_and_map_citations,
)
from .unique_custom.agents import custom_agent
from .unique_custom.citation import GlobalCitationManager
from .unique_custom.utils import (
    create_message_log_entry,
    get_next_message_order,
)


class DeepResearchToolInput(BaseModel):
    model_config = ConfigDict(extra="forbid")

    # Dummy field to be able to use the tool
    start_research: bool = Field(
        description="Whether to start research",
        default=False,
    )


class MemorySchema(BaseModel):
    message_id: str


class DeepResearchTool(Tool[DeepResearchToolConfig]):
    """
    Deep Research Tool for complex, multi-source research tasks.

    This tool performs in-depth research by:
    - Clarifying user intent through interactive questions when needed
    - Generating comprehensive research briefs
    - Conducting research using the configured engine
    - Synthesizing information with proper citations

    Note: This tool is designed for forced invocation and writes directly to the message log.
    Output should not be post-processed as it includes formatted citations and references.
    """

    name = "DeepResearch"

    def __init__(
        self,
        configuration: DeepResearchToolConfig,
        event: ChatEvent,
        tool_progress_reporter,
    ):
        super().__init__(configuration, event, tool_progress_reporter)
        self.chat_id = event.payload.chat_id
        self.company_id = event.company_id
        self.user_id = event.user_id

        self.client = get_async_openai_client()

        self.logger.info(f"Using async OpenAI client pointed to {self.client.base_url}")

        self.content_service = ContentService(
            company_id=self.company_id,
            user_id=self.user_id,
            chat_id=self.chat_id,
            metadata_filter=event.payload.metadata_filter,
        )
        self.memory_service = PersistentShortMemoryManager(
            short_term_memory_service=ShortTermMemoryService(
                company_id=self.company_id,
                user_id=self.user_id,
                chat_id=self.chat_id,
                message_id=None,
            ),
            short_term_memory_schema=MemorySchema,
            short_term_memory_name="deep_research:followup_question_message_id",
        )
        self.env = TEMPLATE_ENV
        self.execution_id = event.payload.message_execution_id

    def takes_control(self) -> bool:
        """
        This tool requires taking control of the conversation from the orchestrator.
        The DeepResearch tool performs complex, multi-step research tasks that involve
        clarifying user intent, generating research briefs, and synthesizing information.
        To ensure the research process is uninterrupted and properly managed, it needs
        to take control of the conversation flow.
        """
        return True

    def is_message_execution(self) -> bool:
        """
        Check if the execution id is valid.
        """
        return self.execution_id is not None

    async def get_followup_question_message_id(self) -> str | None:
        """
        Get the follow-up question message id.
        """

        result = await self.memory_service.load_async()
        if not result:
            return None
        return result.message_id

    async def is_followup_question_answer(self) -> bool:
        """
        Check if the message is a follow-up question answer.
        """
        followup_question_message_id = await self.get_followup_question_message_id()
        if not followup_question_message_id:
            return False
        history = await self.chat_service.get_full_history_async()
        if history and history[-1].id == followup_question_message_id:
            return True
        return False

    @override
    def tool_description(self) -> LanguageModelToolDescription:
        return LanguageModelToolDescription(
            name=self.name,
            description="Use this tool for complex research tasks that require deep investigation",
            parameters=DeepResearchToolInput,
        )

    def tool_description_for_system_prompt(self) -> str:
        return (
            "The DeepResearch tool is for complex research tasks that require:\n"
            "- In-depth investigation across multiple sources\n"
            "- Synthesis of information from various perspectives\n"
            "- Comprehensive analysis with citations\n"
            "- Detailed reports on specific topics\n\n"
        )

    def tool_format_information_for_system_prompt(self) -> str:
        return ""

    def evaluation_check_list(self) -> list[EvaluationMetricName]:
        return [EvaluationMetricName.HALLUCINATION]

    def get_evaluation_checks_based_on_tool_response(
        self, tool_response: ToolCallResponse
    ) -> list[EvaluationMetricName]:
        evaluation_check_list = self.evaluation_check_list()

        # Check if the tool response is empty
        if not tool_response.content_chunks:
            return []
        return evaluation_check_list

    async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
        try:
            return await self._run(tool_call)
        except Exception as e:
            if self.is_message_execution():
                await self._update_execution_status(MessageExecutionUpdateStatus.FAILED)
            self.logger.error(f"Deep Research tool run failed: {e}")
            await self.chat_service.modify_assistant_message_async(
                content="Deep Research failed to complete for an unknown reason",
                set_completed_at=True,
            )
        return ToolCallResponse(
            id=tool_call.id or "",
            name=self.name,
            content="Failed to complete research",
            error_message="Research process failed or returned empty results",
        )

    async def _run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
        self.logger.info("Starting Deep Research tool run")

        await self._clear_original_message()

        # Question answer and message execution will have the same message id, so we need to check if it is a message execution
        if await self.is_followup_question_answer() and not self.is_message_execution():
            self.logger.info("This is a follow-up question answer")
            self.chat_service.create_message_execution(
                message_id=self.event.payload.assistant_message.id,
                type=MessageExecutionType.DEEP_RESEARCH,
            )
            self.write_message_log_text_message(
                "**Waiting for deep research to start**"
            )
            return ToolCallResponse(
                id=tool_call.id or "",
                name=self.name,
                content="",
            )
        if self.is_message_execution():
            self.logger.info("Starting research")
            # Run research
            self.write_message_log_text_message("**Generating research plan**")
            research_brief = await self.generate_research_brief_from_dict(
                self.get_visible_history_messages()
            )
            processed_result, content_chunks = await self.run_research(research_brief)

            # Handle success/failure status updates centrally
            if not processed_result:
                await self._update_execution_status(MessageExecutionUpdateStatus.FAILED)
                self.write_message_log_text_message(
                    "**Research failed for an unknown reason**"
                )
                await self.chat_service.modify_assistant_message_async(
                    content="Deep Research failed to complete for an unknown reason",
                    set_completed_at=True,
                )
                return ToolCallResponse(
                    id=tool_call.id or "",
                    name=self.name,
                    content=processed_result or "Failed to complete research",
                    error_message="Research process failed or returned empty results",
                )

            await self.chat_service.modify_assistant_message_async(
                set_completed_at=True,
            )

            await self._update_execution_status(MessageExecutionUpdateStatus.COMPLETED)

            # Return the results
            return ToolCallResponse(
                id=tool_call.id or "",
                name=self.name,
                content=processed_result,
                content_chunks=content_chunks,
            )

        # Ask followup questions
        followup_question_message = await self.clarify_user_request()
        await self.chat_service.modify_assistant_message_async(
            set_completed_at=True,
        )
        await self._update_tool_debug_info()
        # put message in short term memory to remember that we asked the followup questions
        await self.memory_service.save_async(
            MemorySchema(message_id=self.event.payload.assistant_message.id),
        )
        return ToolCallResponse(
            id=tool_call.id or "",
            name=self.name,
            content=followup_question_message,
        )

    async def _update_execution_status(
        self, status: MessageExecutionUpdateStatus, percentage: Optional[int] = None
    ) -> None:
        """
        Centralized method to update message execution status.

        Args:
            status: The execution status to set
            percentage: Optional completion percentage (defaults based on status)
        """
        if percentage is None:
            percentage = 100 if status == MessageExecutionUpdateStatus.COMPLETED else 0

        await self.chat_service.update_message_execution_async(
            message_id=self.event.payload.assistant_message.id,
            status=status,
            percentage_completed=percentage,
        )

    async def _update_tool_debug_info(self) -> None:
        """
        Update debug info for the tool execution.
        Note: Tool call logging should be handled by the orchestrator.
        This is a workaround for the deep research tool calling the orchestrator multiple times.
        """
        tools = [
            {
                "name": self.name,
                "info": {
                    "is_forced": True,
                    "is_exclusive": True,
                    "loop_iteration": 0,
                },
            }
        ]
        debug_info_event = {
            "tools": tools,
            "assistant": {
                "id": self.event.payload.assistant_id,
                "name": self.event.payload.name,
            },
            "chosenModule": self.event.payload.name,
            "userMetadata": self.event.payload.user_metadata,
            "toolParameters": self.event.payload.tool_parameters,
        }
        await self.chat_service.update_debug_info_async(debug_info=debug_info_event)

    def write_message_log_text_message(self, text: str):
        create_message_log_entry(
            self.chat_service,
            self.event.payload.assistant_message.id,
            text,
            MessageLogStatus.COMPLETED,
            details=MessageLogDetails(data=[]),
            uncited_references=MessageLogUncitedReferences(data=[]),
        )

    def get_visible_history_messages(
        self, messages_to_take: int = 2
    ) -> list[ChatCompletionMessageParam]:
        """
        Get the history messages for the research brief.
        """
        history = self.chat_service.get_full_history()
        history_messages = []
        # Take last user and assistant message pair (assuming it's the clarifying question and answer)
        for msg in history[-messages_to_take:]:
            if msg.role == "user" or msg.role == "assistant":
                history_messages.append(
                    {
                        "role": msg.role.value,
                        "content": msg.content or "",
                    }
                )
        history_messages.append(
            {
                "role": "user",
                "content": self.get_user_request(),
            }
        )
        return history_messages

    async def run_research(self, research_brief: str) -> tuple[str, list[Any]]:
        """
        Run the research using the configured strategy.
        Returns a tuple of (processed_result, content_chunks)
        """
        try:
            result = "", []
            match self.config.engine.get_type():
                case DeepResearchEngine.OPENAI:
                    self.logger.info("Running OpenAI research")
                    result = await self.openai_research(research_brief)
                case DeepResearchEngine.UNIQUE:
                    self.logger.info("Running Custom research")
                    result = await self.custom_research(research_brief)
            self.write_message_log_text_message("**Research done**")
            return result
        except Exception as e:
            self.logger.error(f"Research failed: {e}")
            return "", []

    async def custom_research(self, research_brief: str) -> tuple[str, list[Any]]:
        """
        Run Custom research using LangGraph multi-agent orchestration.
        Returns a tuple of (processed_result, content_chunks)
        """
        try:
            # Create citation manager for this research session
            citation_manager = GlobalCitationManager()

            # Initialize LangGraph state with required services
            initial_state = {
                "messages": [HumanMessage(content=research_brief)],
                "research_brief": research_brief,
                "notes": [],
                "final_report": "",
                "chat_service": self.chat_service,
                "message_id": self.event.payload.assistant_message.id,
            }

            # Prepare configuration for LangGraph
            additional_openai_proxy_headers = {
                "x-user-id": self.user_id,
                "x-chat-id": self.chat_id,
                "x-assistant-id": self.event.payload.assistant_id,
            }
            # Extract tool enablement settings from engine config if it's a UniqueEngine
            enable_web_tools = True
            enable_internal_tools = True
            if isinstance(self.config.engine, UniqueEngine):
                enable_web_tools = self.config.engine.tools.web_tools
                enable_internal_tools = self.config.engine.tools.internal_tools

            config = {
                "configurable": {
                    "engine_config": self.config.engine,
                    "openai_client": self.client,
                    "chat_service": self.chat_service,
                    "content_service": self.content_service,
                    "message_id": self.event.payload.assistant_message.id,
                    "citation_manager": citation_manager,
                    "additional_openai_proxy_headers": additional_openai_proxy_headers,
                    "enable_web_tools": enable_web_tools,
                    "enable_internal_tools": enable_internal_tools,
                },
            }

            result = await custom_agent.ainvoke(initial_state, config=config)  # type: ignore[arg-type]

            # Extract final report (citations already refined by agents.py)
            research_result = result.get("final_report", "")

            if not research_result:
                return "", []

            # Validate and map citations using the citation registry
            citation_registry = citation_manager.get_all_citations()
            processed_result, references = validate_and_map_citations(
                research_result, citation_registry
            )

            # Update the assistant message with the results
            await self.chat_service.modify_assistant_message_async(
                content=processed_result,
                references=references,
            )
            self.logger.info(
                f"Custom research completed with {len(references)} validated citations"
            )
            return processed_result, []

        except Exception as e:
            error_msg = f"Custom research failed: {str(e)}"
            self.logger.error(error_msg, exc_info=True)
            return error_msg, []

    async def openai_research(self, research_brief: str) -> tuple[str, list[Any]]:
        """
        Run OpenAI-specific research.
        Returns a tuple of (processed_result, content_chunks)
        """
        stream = await self.client.responses.create(
            timeout=RESPONSES_API_TIMEOUT_SECONDS,
            model=self.config.engine.research_model.name,
            input=[
                {
                    "role": "developer",
                    "content": [
                        {
                            "type": "input_text",
                            "text": self.env.get_template(
                                "openai/oai_research_system_message.j2"
                            ).render(),
                        }
                    ],
                },
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "input_text",
                            "text": research_brief,
                        }
                    ],
                },
            ],
            reasoning={"summary": "auto"},
            tools=[
                {"type": "web_search_preview"},
            ],
            stream=True,
        )

        # Process the stream
        research_result, annotations = await self._process_research_stream(stream)

        if not research_result:
            return "", []

        # Postprocess research result to extract links, create references and chunks
        processed_result, _, content_chunks = postprocess_research_result_with_chunks(
            research_result, tool_call_id="", message_id=""
        )
        # Beautify the report
        processed_result = await self._postprocess_report_with_gpt(processed_result)

        # Convert OpenAI annotations to link references
        link_references = self._convert_annotations_to_references(
            annotations or [], message_id=""
        )

        # Update the assistant message with the results
        await self.chat_service.modify_assistant_message_async(
            content=processed_result,
            references=link_references,
        )

        return processed_result, content_chunks

    async def _process_research_stream(self, stream) -> tuple[str, list[Annotation]]:
        """
        Process the OpenAI Realtime API stream and extract the final report.

        Args:
            stream: Stream of ResponseStreamEvent objects from OpenAI Realtime API

        Returns:
            Tuple of (report_text, annotations)
        """

        async for event in stream:
            try:
                match event.type:
                    case "response.completed":
                        if event.response.usage:
                            self.logger.info(
                                f"OpenAI research token usage: {event.response.usage}"
                            )
                        # Extract the final output with annotations
                        if event.response.output and len(event.response.output) > 0:
                            final_output = event.response.output[-1]
                            if not isinstance(final_output, ResponseOutputMessage):
                                self.logger.warning(
                                    f"Unexpected output type: {type(final_output)}"
                                )
                                continue
                            content_item = final_output.content[0]

                            # Refusal
                            if isinstance(content_item, ResponseOutputRefusal):
                                return content_item.refusal, []

                            # Extract final report and references
                            report_text = content_item.text
                            annotations = content_item.annotations or []
                            self.logger.info(
                                "Final report extracted from OpenAI stream"
                            )
                            return report_text, annotations
                        return event.response.output_text or "", []
                    case "response.incomplete":
                        if event.response.incomplete_details:
                            return (
                                event.response.incomplete_details.reason
                                or "Incomplete due to unknown reason",
                                [],
                            )
                    case "response.output_item.done":
                        # This is where we handle the output items and send to frontend
                        if isinstance(event.item, ResponseReasoningItem):
                            for summary in event.item.summary:
                                self.chat_service.create_message_log(
                                    message_id=self.event.payload.assistant_message.id,
                                    text=summary.text,
                                    status=MessageLogStatus.COMPLETED,
                                    order=get_next_message_order(
                                        self.event.payload.assistant_message.id
                                    ),
                                    uncited_references=MessageLogUncitedReferences(
                                        data=[],
                                    ),
                                    details=MessageLogDetails(
                                        data=[],
                                    ),
                                )
                        elif isinstance(event.item, ResponseFunctionWebSearch):
                            if isinstance(
                                event.item.action, ActionSearch
                            ) and isinstance(event.item.action.query, str):
                                self.logger.info("OpenAI web search")
                                self.chat_service.create_message_log(
                                    message_id=self.event.payload.assistant_message.id,
                                    text="**Searching the web**",
                                    status=MessageLogStatus.COMPLETED,
                                    order=get_next_message_order(
                                        self.event.payload.assistant_message.id
                                    ),
                                    uncited_references=MessageLogUncitedReferences(
                                        data=[],
                                    ),
                                    details=MessageLogDetails(
                                        data=[
                                            MessageLogEvent(
                                                type="WebSearch",
                                                text=event.item.action.query,
                                            )
                                        ],
                                    ),
                                )
                            elif isinstance(
                                event.item.action, ActionOpenPage
                            ) or isinstance(event.item.action, ActionFind):
                                self.logger.info("OpenAI reading web page")
                                if (
                                    not event.item.action.url
                                    or not isinstance(event.item.action.url, str)
                                    or "https://" not in event.item.action.url
                                ):
                                    self.logger.warning(
                                        f"Invalid URL from OpenAI: {event.item.action}"
                                    )
                                    continue

                                _, title, success = await crawl_url(
                                    AsyncClient(follow_redirects=True),
                                    event.item.action.url,
                                )
                                if not success:
                                    self.logger.info(
                                        f"Failed to crawl URL: {event.item.action.url} but openai still opened the page"
                                    )
                                    continue
                                if not title:
                                    self.logger.info(
                                        f"No title found for URL: {event.item.action.url}"
                                    )
                                    continue
                                self.chat_service.create_message_log(
                                    message_id=self.event.payload.assistant_message.id,
                                    text="**Reading website**",
                                    status=MessageLogStatus.COMPLETED,
                                    order=get_next_message_order(
                                        self.event.payload.assistant_message.id
                                    ),
                                    uncited_references=MessageLogUncitedReferences(
                                        data=[
                                            ContentReference(
                                                name=title or event.item.action.url,
                                                url=event.item.action.url,
                                                sequence_number=0,
                                                source="web",
                                                source_id=event.item.action.url,
                                            )
                                        ],
                                    ),
                                    details=MessageLogDetails(
                                        data=[],
                                    ),
                                )
                            else:
                                self.logger.info(
                                    f"OpenAI web action unexpected type: {type(event.item)}"
                                )
                    case "response.failed":
                        self.chat_service.create_message_log(
                            message_id=self.event.payload.assistant_message.id,
                            text=f"Failed to complete research: {event.response.error}",
                            status=MessageLogStatus.FAILED,
                            order=get_next_message_order(
                                self.event.payload.assistant_message.id
                            ),
                            uncited_references=MessageLogUncitedReferences(
                                data=[],
                            ),
                            details=MessageLogDetails(
                                data=[],
                            ),
                        )
                        if event.response.error:
                            return event.response.error.message, []
            except Exception as e:
                self.logger.error(f"Error processing research stream event: {e}")

        self.logger.error("Stream ended without completion")
        return "", []

    async def _postprocess_report_with_gpt(self, research_result: str) -> str:
        """
        Post-process the research report with GPT-4.1 to improve markdown formatting.
        Preserves all sup tags while enhancing the overall formatting and readability.

        Args:
            research_result: The raw research result from OpenAI

        Returns:
            The post-processed research result with improved formatting
        """
        self.write_message_log_text_message("**Synthesizing final research report**")

        response = await self.client.chat.completions.create(
            model=self.config.engine.large_model.name,
            messages=[
                {
                    "role": "system",
                    "content": self.env.get_template(
                        "report_cleanup_prompt.j2"
                    ).render(),
                },
                {
                    "role": "user",
                    "content": f"Please improve the markdown formatting of this research report:\n\n{research_result}",
                },
            ],
            temperature=0.1,
            max_tokens=5000,
        )

        formatted_result = response.choices[0].message.content
        if formatted_result:
            self.logger.info("Successfully post-processed research report")
            return formatted_result
        else:
            self.logger.warning("Post-processing returned empty result, using original")
            return research_result

    def get_tool_call_result_for_loop_history(
        self,
        tool_response: ToolCallResponse,
        agent_chunks_handler: AgentChunksHandler,  # noqa: ARG002
    ) -> LanguageModelMessage:
        """
        Convert tool response to message format for conversation history.

        Since DeepResearch writes directly to message logs and takes control,
        we return the content without additional processing.
        """
        return LanguageModelMessage(
            role=LanguageModelMessageRole.TOOL,
            content=tool_response.content,
        )

    def get_user_request(self) -> str:
        """
        Get the user's request.
        """
        return (
            self.event.payload.user_message.text
            or self.event.payload.user_message.original_text
            or ""
        )

    async def clarify_user_request(self) -> str:
        """
        Clarify the user's request.
        """
        # # Get user query
        relevant_interactions = self.get_visible_history_messages(4)

        # Step 1: Generate clarifying questions
        messages: list[ChatCompletionMessageParam] = [
            {
                "role": "system",
                "content": self.env.get_template("clarifying_agent.j2").render(
                    engine_type=self.config.engine.get_type().value
                ),
            },
            *relevant_interactions,
        ]
        response = await self.chat_service.complete_async(
            messages=messages,
            model_name=self.config.engine.small_model.name,
            content_chunks=None,
        )
        assert isinstance(response.choices[0].message.content, str), (
            "No clarifying questions generated"
        )
        return response.choices[0].message.content

    async def generate_research_brief_from_dict(
        self, messages: list[ChatCompletionMessageParam]
    ) -> str:
        """Generate research brief from dictionary messages."""
        chat_messages: list[Any] = [
            {
                "role": "system",
                "content": self.env.get_template(
                    "research_instructions_agent.j2"
                ).render(engine_type=self.config.engine.get_type().value),
            }
        ] + messages

        research_response = await self.client.chat.completions.create(
            model=self.config.engine.large_model.name,
            messages=chat_messages,
        )

        research_instructions = research_response.choices[0].message.content
        assert research_instructions, "No research instructions generated"
        return research_instructions

    async def generate_research_brief(
        self, messages: list[LanguageModelMessage]
    ) -> str | None:
        """Generate research brief from LanguageModelMessage objects."""
        # Convert to dict format and use the other method
        dict_messages = []
        for message in messages:
            if message.content:
                dict_messages.append(
                    {
                        "role": message.role.value
                        if hasattr(message.role, "value")
                        else str(message.role),
                        "content": message.content,
                    }
                )
        return await self.generate_research_brief_from_dict(dict_messages)

    def _convert_annotations_to_references(
        self, annotations: list[Annotation], message_id: str | None = None
    ) -> list[ContentReference]:
        """Convert Deep Research annotations to ContentReferences.

        Args:
            annotations: List of annotation objects from Deep Research API
            message_id: Message ID for the references (defaults to empty string for current message)

        Returns:
            List of ContentReference objects
        """
        references = []
        msg_id = message_id or ""

        for i, annotation in enumerate(annotations):
            if not isinstance(annotation, AnnotationURLCitation):
                continue
            # Create ContentReference directly
            reference = ContentReference(
                message_id=msg_id,
                name=annotation.title or f"Deep Research Citation {i + 1}",
                sequence_number=i + 1,
                source="deep-research-citations",
                source_id=annotation.url,
                url=annotation.url,
            )
            references.append(reference)

        return references

    async def _clear_original_message(self) -> None:
        """
        Clear the original message.
        """
        await self.chat_service.modify_assistant_message_async(
            original_content="",
        )


# Register the tool with the ToolFactory
ToolFactory.register_tool(DeepResearchTool, DeepResearchToolConfig)
