BareGit
import requests
from typing import Dict, Any, List


class TriliumClient:
    def __init__(self, url: str, token: str):
        self.url = url.rstrip("/")
        self.token = token
        # ETAPI requires "Authorization: Bearer <token>"
        self.auth_header = {
            "Authorization": f"Bearer {self.token}",
            "User-Agent": "TriliumMCP/1.0"
        }

    def _get_headers(self, content_type: str = None) -> Dict[str, str]:
        headers = self.auth_header.copy()
        if content_type:
            headers["Content-Type"] = content_type
        return headers

    def searchNotes(self, query: str) -> List[Dict[str, Any]]:
        """Search for notes using Trilium's search syntax."""
        response = requests.get(
            f"{self.url}/etapi/notes",
            params={"search": query},
            headers=self._get_headers()
        )
        response.raise_for_status()
        return response.json()

    def getNoteContent(self, note_id: str) -> str:
        """Fetch raw content of a note."""
        # Note: /etapi/notes/{noteId}/content returns raw HTML
        response = requests.get(
            f"{self.url}/etapi/notes/{note_id}/content",
            headers=self._get_headers()
        )
        response.raise_for_status()
        return response.text

    def getNoteMetadata(self, note_id: str) -> Dict[str, Any]:
        """Fetch full note metadata and attributes."""
        response = requests.get(
            f"{self.url}/etapi/notes/{note_id}",
            headers=self._get_headers()
        )
        response.raise_for_status()
        return response.json()

    def getNoteChildren(self, note_id: str) -> List[Dict[str, Any]]:
        """List children of a specified note."""
        # ancestorNoteId and ancestorDepth=eq1 is the supported way to list direct children
        # the search parameter is mandatory, so we use a generic query that matches all notes
        params = {
            "search": "note.title *= ''",
            "ancestorNoteId": note_id,
            "ancestorDepth": "eq1"
        }
        response = requests.get(
            f"{self.url}/etapi/notes",
            params=params,
            headers=self._get_headers()
        )
        response.raise_for_status()
        return response.json()

    def createNote(self, parent_note_id: str, title: str, content: str, type: str = "text") -> Dict[str, Any]:
        """Create a new note under a specified parent."""
        payload = {
            "parentNoteId": parent_note_id,
            "title": title,
            "content": content,
            "type": type
        }
        # Note: Using /etapi/create-note instead of /etapi/notes for compatibility
        response = requests.post(
            f"{self.url}/etapi/create-note",
            json=payload,
            headers=self._get_headers("application/json")
        )
        response.raise_for_status()
        return response.json()

    def createAttribute(self, note_id: str, type: str, name: str, value: str, is_inheritable: bool = False) -> Dict[str, Any]:
        """Create a new attribute (label or relation) for a note."""
        payload = {
            "noteId": note_id,
            "type": type,
            "name": name,
            "value": value,
            "isInheritable": is_inheritable
        }
        response = requests.post(
            f"{self.url}/etapi/attributes",
            json=payload,
            headers=self._get_headers("application/json")
        )
        response.raise_for_status()
        return response.json()

    def updateNoteContent(self, note_id: str, content: str) -> None:
        """Update content of an existing note."""
        # ETAPI uses PUT /etapi/notes/{noteId}/content for updating content
        response = requests.put(
            f"{self.url}/etapi/notes/{note_id}/content",
            data=content.encode('utf-8'),
            headers=self._get_headers("text/plain")
        )
        response.raise_for_status()