"""Posts Handler - manages WordPress posts, pages, and custom post types"""

import asyncio
import json
import re
from typing import Any

from plugins.wordpress.client import WordPressClient


def _count_words(html_content: str) -> int:
    """Strip HTML tags and count words."""
    text = re.sub(r"<[^>]+>", " ", html_content)
    text = re.sub(r"\s+", " ", text).strip()
    return len(text.split()) if text else 0


def _strip_html(html_content: str, max_chars: int = 500) -> str:
    """Strip HTML tags and return first max_chars characters."""
    text = re.sub(r"<[^>]+>", " ", html_content)
    text = re.sub(r"\s+", " ", text).strip()
    if len(text) > max_chars:
        return text[:max_chars] + "..."
    return text


def get_tool_specifications() -> list[dict[str, Any]]:
    """Return tool specifications for ToolGenerator"""
    return [
        # === POSTS ===
        {
            "name": "list_posts",
            "method_name": "list_posts",
            "description": "List WordPress posts. Returns paginated list of posts with title, excerpt, status, and metadata.",
            "schema": {
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "integer",
                        "description": "Number of posts per page (1-100)",
                        "default": 10,
                        "minimum": 1,
                        "maximum": 100,
                    },
                    "page": {
                        "type": "integer",
                        "description": "Page number",
                        "default": 1,
                        "minimum": 1,
                    },
                    "status": {
                        "type": "string",
                        "description": "Filter by post status",
                        "enum": ["publish", "draft", "pending", "private", "any"],
                        "default": "any",
                    },
                    "search": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Search term to filter posts",
                    },
                    "search_terms": {
                        "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
                        "description": "Multiple search terms to search in parallel. Results are deduplicated. Overrides 'search' if both provided.",
                    },
                    "include_content": {
                        "type": "boolean",
                        "description": "Include content summary (first 500 chars) and word count in results. Default false to save tokens.",
                        "default": False,
                    },
                },
            },
            "scope": "read",
        },
        {
            "name": "get_post",
            "method_name": "get_post",
            "description": "Get a specific WordPress post by ID. Returns complete post data including content, metadata, and author information.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "integer",
                        "description": "Post ID to retrieve",
                        "minimum": 1,
                    },
                    "fields": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Comma-separated list of fields to return (e.g., 'id,title,status,tags'). Returns all fields if not specified. Use to reduce response size and token usage.",
                    },
                },
                "required": ["post_id"],
            },
            "scope": "read",
        },
        {
            "name": "create_post",
            "method_name": "create_post",
            "description": "Create a new WordPress post. Supports custom slug, categories, tags, and featured image.",
            "schema": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "Post title (displayed as main heading)",
                        "minLength": 1,
                    },
                    "content": {
                        "type": "string",
                        "description": "Post content (supports HTML and WordPress blocks)",
                    },
                    "status": {
                        "type": "string",
                        "description": "Publication status",
                        "enum": ["publish", "draft", "pending", "private", "future"],
                        "default": "draft",
                    },
                    "slug": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post URL slug (auto-generated from title if not provided)",
                    },
                    "excerpt": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post excerpt/summary",
                    },
                    "categories": {
                        "anyOf": [
                            {"type": "array", "items": {"type": "integer"}},
                            {"type": "null"},
                        ],
                        "description": "Category IDs to assign to post",
                    },
                    "tags": {
                        "anyOf": [
                            {"type": "array", "items": {"type": "integer"}},
                            {"type": "null"},
                        ],
                        "description": "Tag IDs to assign to post",
                    },
                    "featured_media": {
                        "anyOf": [{"type": "integer"}, {"type": "null"}],
                        "description": "Featured image media ID",
                    },
                },
                "required": ["title", "content"],
            },
            "scope": "write",
        },
        {
            "name": "update_post",
            "method_name": "update_post",
            "description": "Update an existing WordPress post. Can update any field including title, content, status, categories, and tags.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "integer",
                        "description": "Post ID to update",
                        "minimum": 1,
                    },
                    "title": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post title",
                    },
                    "content": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post content",
                    },
                    "status": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Publication status",
                        "enum": ["publish", "draft", "pending", "private", "future"],
                    },
                    "slug": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post URL slug",
                    },
                    "excerpt": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Post excerpt",
                    },
                    "categories": {
                        "anyOf": [
                            {"type": "array", "items": {"type": "integer"}},
                            {"type": "null"},
                        ],
                        "description": "Category IDs",
                    },
                    "tags": {
                        "anyOf": [
                            {"type": "array", "items": {"type": "integer"}},
                            {"type": "null"},
                        ],
                        "description": "Tag IDs",
                    },
                    "featured_media": {
                        "anyOf": [{"type": "integer"}, {"type": "null"}],
                        "description": "Featured image media ID",
                    },
                },
                "required": ["post_id"],
            },
            "scope": "write",
        },
        {
            "name": "delete_post",
            "method_name": "delete_post",
            "description": "Delete or trash a WordPress post. Can permanently delete or move to trash.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "integer",
                        "description": "Post ID to delete",
                        "minimum": 1,
                    },
                    "force": {
                        "type": "boolean",
                        "description": "Permanently delete (true) or move to trash (false)",
                        "default": False,
                    },
                },
                "required": ["post_id"],
            },
            "scope": "write",
        },
        # === PAGES ===
        {
            "name": "list_pages",
            "method_name": "list_pages",
            "description": "List WordPress pages. Returns paginated list of pages with metadata.",
            "schema": {
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "integer",
                        "description": "Number of pages per page (1-100)",
                        "default": 10,
                        "minimum": 1,
                        "maximum": 100,
                    },
                    "page": {
                        "type": "integer",
                        "description": "Page number",
                        "default": 1,
                        "minimum": 1,
                    },
                    "status": {
                        "type": "string",
                        "description": "Filter by page status",
                        "enum": ["publish", "draft", "pending", "private", "any"],
                        "default": "any",
                    },
                },
            },
            "scope": "read",
        },
        {
            "name": "create_page",
            "method_name": "create_page",
            "description": "Create a new WordPress page. Supports HTML content, parent pages, and custom slugs.",
            "schema": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "Page title (displayed as main heading)",
                        "minLength": 1,
                    },
                    "content": {
                        "type": "string",
                        "description": "Page content (supports HTML and WordPress blocks)",
                    },
                    "status": {
                        "type": "string",
                        "description": "Publication status of the page",
                        "enum": ["publish", "draft", "pending", "private"],
                        "default": "draft",
                    },
                    "slug": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Page URL slug (e.g., 'about-us'). Auto-generated from title if not provided.",
                    },
                    "parent": {
                        "anyOf": [{"type": "integer"}, {"type": "null"}],
                        "description": "Parent page ID for creating hierarchical page structure",
                    },
                },
                "required": ["title", "content"],
            },
            "scope": "write",
        },
        {
            "name": "update_page",
            "method_name": "update_page",
            "description": "Update an existing WordPress page. Can update title, content, status, slug, parent page, featured image, and post meta (e.g. Yoast fields).",
            "schema": {
                "type": "object",
                "properties": {
                    "page_id": {
                        "type": "integer",
                        "description": "Page ID to update",
                        "minimum": 1,
                    },
                    "title": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Page title",
                    },
                    "content": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Page content",
                    },
                    "status": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Publication status",
                        "enum": ["publish", "draft", "pending", "private"],
                    },
                    "slug": {
                        "anyOf": [{"type": "string"}, {"type": "null"}],
                        "description": "Page URL slug",
                    },
                    "parent": {
                        "anyOf": [{"type": "integer"}, {"type": "null"}],
                        "description": "Parent page ID",
                    },
                    "featured_media": {
                        "anyOf": [{"type": "integer"}, {"type": "null"}],
                        "description": "Featured image attachment ID. Pass 0 to clear.",
                    },
                    "meta": {
                        "anyOf": [{"type": "object"}, {"type": "null"}],
                        "description": "Post meta key/value map forwarded as REST `meta` (e.g. Yoast fields like `_yoast_wpseo_metadesc`). Only registered/whitelisted keys are persisted by WordPress.",
                    },
                },
                "required": ["page_id"],
            },
            "scope": "write",
        },
        {
            "name": "delete_page",
            "method_name": "delete_page",
            "description": "Delete or trash a WordPress page. Can permanently delete or move to trash.",
            "schema": {
                "type": "object",
                "properties": {
                    "page_id": {
                        "type": "integer",
                        "description": "Page ID to delete",
                        "minimum": 1,
                    },
                    "force": {
                        "type": "boolean",
                        "description": "Permanently delete (true) or move to trash (false)",
                        "default": False,
                    },
                },
                "required": ["page_id"],
            },
            "scope": "write",
        },
        # === POST TYPES ===
        {
            "name": "list_post_types",
            "method_name": "list_post_types",
            "description": "List all registered post types including built-in (post, page) and custom post types (portfolio, testimonials, etc.).",
            "schema": {"type": "object", "properties": {}},
            "scope": "read",
        },
        {
            "name": "get_post_type_info",
            "method_name": "get_post_type_info",
            "description": "Get detailed information about a specific post type including supported features and REST API configuration.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_type": {
                        "type": "string",
                        "description": "Post type slug (e.g., 'portfolio', 'post', 'page')",
                    }
                },
                "required": ["post_type"],
            },
            "scope": "read",
        },
        # === CUSTOM POSTS ===
        {
            "name": "list_custom_posts",
            "method_name": "list_custom_posts",
            "description": "List posts of a specific custom post type. Use list_post_types first to discover available post types.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_type": {
                        "type": "string",
                        "description": "Post type slug (e.g., 'portfolio', 'testimonials')",
                    },
                    "per_page": {
                        "type": "integer",
                        "description": "Number of posts per page",
                        "default": 10,
                        "minimum": 1,
                        "maximum": 100,
                    },
                    "page": {
                        "type": "integer",
                        "description": "Page number",
                        "default": 1,
                        "minimum": 1,
                    },
                    "status": {
                        "type": "string",
                        "description": "Post status filter",
                        "enum": ["publish", "draft", "pending", "private", "any"],
                        "default": "any",
                    },
                },
                "required": ["post_type"],
            },
            "scope": "read",
        },
        {
            "name": "create_custom_post",
            "method_name": "create_custom_post",
            "description": "Create a new post of a custom post type. Supports custom fields/meta data.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_type": {
                        "type": "string",
                        "description": "Post type slug (e.g., 'portfolio')",
                    },
                    "title": {"type": "string", "description": "Post title", "minLength": 1},
                    "content": {"type": "string", "description": "Post content (HTML allowed)"},
                    "status": {
                        "type": "string",
                        "description": "Post status",
                        "enum": ["publish", "draft", "pending", "private", "future"],
                        "default": "draft",
                    },
                    "meta": {
                        "anyOf": [{"type": "object"}, {"type": "null"}],
                        "description": "Custom fields/meta data",
                    },
                },
                "required": ["post_type", "title", "content"],
            },
            "scope": "write",
        },
        # === INTERNAL LINKS ===
        {
            "name": "get_internal_links",
            "method_name": "get_internal_links",
            "description": "Extract internal links from a post's content. Returns list of internal links with anchor text and URL. Useful for SEO internal linking analysis and avoiding duplicate links.",
            "schema": {
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "integer",
                        "description": "Post ID to analyze for internal links",
                        "minimum": 1,
                    }
                },
                "required": ["post_id"],
            },
            "scope": "read",
        },
    ]


class PostsHandler:
    """Handle post-related operations for WordPress"""

    def __init__(self, client: WordPressClient):
        """
        Initialize posts handler.

        Args:
            client: WordPress API client instance
        """
        self.client = client

    # === POSTS ===

    async def list_posts(
        self,
        per_page: int = 10,
        page: int = 1,
        status: str = "any",
        search: str | None = None,
        search_terms: list[str] | None = None,
        include_content: bool = False,
    ) -> str:
        """
        List WordPress posts.

        Args:
            per_page: Number of posts per page (1-100)
            page: Page number
            status: Post status filter (publish, draft, pending, private, any)
            search: Search term to filter posts
            search_terms: Multiple search terms for parallel search with deduplication
            include_content: Include content summary and word count in results

        Returns:
            JSON string with posts list
        """
        try:
            params = {
                "per_page": per_page,
                "page": page,
                "status": status,
                "_embed": "true",  # Include author and featured image
            }

            # Multi-search: parallel API calls with deduplication
            if search_terms and len(search_terms) > 0:

                async def _search_single(term: str) -> list:
                    p = {**params, "search": term}
                    return await self.client.get("posts", params=p)

                batches = await asyncio.gather(
                    *[_search_single(term) for term in search_terms], return_exceptions=True
                )
                seen_ids: set = set()
                posts: list = []
                for batch in batches:
                    if isinstance(batch, Exception):
                        continue
                    for post in batch:
                        if post["id"] not in seen_ids:
                            seen_ids.add(post["id"])
                            posts.append(post)
            else:
                if search:
                    params["search"] = search
                posts = await self.client.get("posts", params=params)

            # Format response
            def _format_post(post: dict) -> dict:
                item = {
                    "id": post["id"],
                    "title": post["title"]["rendered"],
                    "excerpt": post["excerpt"]["rendered"][:200],
                    "status": post["status"],
                    "date": post["date"],
                    "author": post.get("_embedded", {})
                    .get("author", [{}])[0]
                    .get("name", "Unknown"),
                    "link": post["link"],
                }
                if include_content:
                    content_html = post.get("content", {}).get("rendered", "")
                    item["content_summary"] = _strip_html(content_html, 500)
                    item["word_count"] = _count_words(content_html)
                return item

            result = {
                "total": len(posts),
                "page": page,
                "per_page": per_page,
                "posts": [_format_post(post) for post in posts],
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to list posts: {str(e)}"}, indent=2
            )

    async def get_post(self, post_id: int, fields: str | None = None) -> str:
        """
        Get a specific WordPress post.

        Args:
            post_id: Post ID to retrieve
            fields: Comma-separated list of fields to return (e.g., 'id,title,status').
                When set, behaves as a STRICT allow-list — only the
                requested names (plus ``id``) are returned. Unknown
                names are ignored.

        Returns:
            JSON string with post data. Default projection includes
            ``featured_media`` (int or 0), ``slug`` (str), and
            ``featured_media_url`` (derived from
            ``_embedded['wp:featuredmedia'][0].source_url``; empty
            string when no featured image is set) so callers can
            verify a post's featured image was set without a separate
            media-fetch round trip.
        """
        try:
            # F.X.fix #4: default projection includes the fields every
            # caller asked for during F.X.test (featured_media, slug,
            # featured_media_url). When ``fields`` is supplied we honour
            # it as a STRICT allow-list — callers must opt in to each
            # name explicitly, no hidden always-on subset beyond id.
            params = {"_embed": "true"}
            field_map = {
                "id": "id",
                "title": "title",
                "content": "content",
                "excerpt": "excerpt",
                "status": "status",
                "date": "date",
                "modified": "modified",
                "author": "_embedded",
                "categories": "categories",
                "tags": "tags",
                "link": "link",
                "slug": "slug",
                "featured_media": "featured_media",
                "featured_media_url": "_embedded",
                "word_count": "content",
            }
            if fields:
                wp_fields: set[str] = set()
                requested = {f.strip().lower() for f in fields.split(",") if f.strip()}
                for f in requested:
                    if f in field_map:
                        wp_fields.add(field_map[f])
                wp_fields.add("id")
                params["_fields"] = ",".join(wp_fields)

            post = await self.client.get(f"posts/{post_id}", params=params)

            # Derive featured_media_url from the embedded media entry.
            # WP returns the embedded resource under
            # ``_embedded['wp:featuredmedia']`` (hyphenated key, list of
            # one). Empty string when no featured image is attached.
            featured_media_url = ""
            embedded = post.get("_embedded") or {}
            media_entries = embedded.get("wp:featuredmedia") or []
            if isinstance(media_entries, list) and media_entries:
                first = media_entries[0] or {}
                if isinstance(first, dict):
                    featured_media_url = first.get("source_url") or ""

            # Build full result
            full_result = {
                "id": post["id"],
                "title": post.get("title", {}).get("rendered", ""),
                "slug": post.get("slug", ""),
                "content": post.get("content", {}).get("rendered", ""),
                "excerpt": post.get("excerpt", {}).get("rendered", ""),
                "status": post.get("status", ""),
                "date": post.get("date", ""),
                "modified": post.get("modified", ""),
                "author": embedded.get("author", [{}])[0].get("name", "Unknown"),
                "categories": post.get("categories", []),
                "tags": post.get("tags", []),
                "link": post.get("link", ""),
                "featured_media": post.get("featured_media", 0) or 0,
                "featured_media_url": featured_media_url,
                "word_count": _count_words(post.get("content", {}).get("rendered", "")),
            }

            # Strict allow-list filtering when ``fields`` is provided.
            if fields:
                requested = {f.strip().lower() for f in fields.split(",") if f.strip()}
                requested.add("id")
                result = {k: v for k, v in full_result.items() if k in requested}
            else:
                result = full_result

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to get post {post_id}: {str(e)}"}, indent=2
            )

    async def create_post(
        self,
        title: str,
        content: str,
        status: str = "draft",
        slug: str | None = None,
        excerpt: str | None = None,
        categories: list[int] | None = None,
        tags: list[int] | None = None,
        featured_media: int | None = None,
    ) -> str:
        """
        Create a new WordPress post.

        Args:
            title: Post title
            content: Post content (HTML allowed)
            status: Publication status (draft, publish, pending, private, future)
            slug: Post URL slug (auto-generated if not provided)
            excerpt: Post excerpt/summary
            categories: Category IDs to assign
            tags: Tag IDs to assign
            featured_media: Featured image media ID

        Returns:
            JSON string with created post data
        """
        try:
            data = {"title": title, "content": content, "status": status}
            if slug:
                data["slug"] = slug
            if excerpt:
                data["excerpt"] = excerpt
            if categories:
                data["categories"] = categories
            if tags:
                data["tags"] = tags
            if featured_media:
                data["featured_media"] = featured_media

            post = await self.client.post("posts", json_data=data)

            result = {
                "id": post["id"],
                "title": post["title"]["rendered"],
                "status": post["status"],
                "link": post["link"],
                "message": f"Post created successfully with ID {post['id']}",
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to create post: {str(e)}"}, indent=2
            )

    async def update_post(
        self,
        post_id: int,
        title: str | None = None,
        content: str | None = None,
        status: str | None = None,
        slug: str | None = None,
        excerpt: str | None = None,
        categories: list[int] | None = None,
        tags: list[int] | None = None,
        featured_media: int | None = None,
    ) -> str:
        """
        Update an existing WordPress post.

        Args:
            post_id: Post ID to update
            title: Post title
            content: Post content
            status: Publication status
            slug: Post URL slug
            excerpt: Post excerpt
            categories: Category IDs
            tags: Tag IDs
            featured_media: Featured image media ID

        Returns:
            JSON string with updated post data
        """
        try:
            # Build data dict with only provided values
            data = {}
            if title is not None:
                data["title"] = title
            if content is not None:
                data["content"] = content
            if status is not None:
                data["status"] = status
            if slug is not None:
                data["slug"] = slug
            if excerpt is not None:
                data["excerpt"] = excerpt
            if categories is not None:
                data["categories"] = categories
            if tags is not None:
                data["tags"] = tags
            if featured_media is not None:
                data["featured_media"] = featured_media

            post = await self.client.post(f"posts/{post_id}", json_data=data)

            result = {
                "id": post["id"],
                "title": post["title"]["rendered"],
                "status": post["status"],
                "link": post["link"],
                "message": f"Post {post_id} updated successfully",
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to update post {post_id}: {str(e)}"}, indent=2
            )

    async def delete_post(self, post_id: int, force: bool = False) -> str:
        """
        Delete or trash a WordPress post.

        Args:
            post_id: Post ID to delete
            force: Permanently delete (True) or move to trash (False)

        Returns:
            JSON string with deletion result
        """
        try:
            params = {"force": "true" if force else "false"}
            result = await self.client.delete(f"posts/{post_id}", params=params)

            message = f"Post {post_id} {'permanently deleted' if force else 'moved to trash'}"
            return json.dumps({"success": True, "message": message, "result": result}, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to delete post {post_id}: {str(e)}"}, indent=2
            )

    # === PAGES ===

    async def list_pages(self, per_page: int = 10, page: int = 1, status: str = "any") -> str:
        """
        List WordPress pages.

        Args:
            per_page: Number of pages per page (1-100)
            page: Page number
            status: Page status filter

        Returns:
            JSON string with pages list
        """
        try:
            params = {"per_page": per_page, "page": page, "status": status}
            pages = await self.client.get("pages", params=params)

            result = {
                "total": len(pages),
                "pages": [
                    {
                        "id": p["id"],
                        "title": p["title"]["rendered"],
                        "status": p["status"],
                        "date": p["date"],
                        "link": p["link"],
                    }
                    for p in pages
                ],
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to list pages: {str(e)}"}, indent=2
            )

    async def create_page(
        self,
        title: str,
        content: str,
        status: str = "draft",
        slug: str | None = None,
        parent: int | None = None,
    ) -> str:
        """
        Create a new WordPress page.

        Args:
            title: Page title
            content: Page content (HTML allowed)
            status: Publication status (draft, publish, pending, private)
            slug: Page URL slug (auto-generated if not provided)
            parent: Parent page ID for hierarchical structure

        Returns:
            JSON string with created page data
        """
        try:
            data = {"title": title, "content": content, "status": status}
            if slug:
                data["slug"] = slug
            if parent:
                data["parent"] = parent

            page = await self.client.post("pages", json_data=data)

            result = {
                "id": page["id"],
                "title": page["title"]["rendered"],
                "status": page["status"],
                "link": page["link"],
                "message": f"Page created successfully with ID {page['id']}",
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to create page: {str(e)}"}, indent=2
            )

    async def update_page(
        self,
        page_id: int,
        title: str | None = None,
        content: str | None = None,
        status: str | None = None,
        slug: str | None = None,
        parent: int | None = None,
        featured_media: int | None = None,
        meta: dict | None = None,
    ) -> str:
        """
        Update an existing WordPress page.

        Args:
            page_id: Page ID to update
            title: Page title
            content: Page content
            status: Publication status
            slug: Page URL slug
            parent: Parent page ID
            featured_media: Featured image attachment ID (0 clears it)
            meta: Post meta key/value map (e.g. Yoast fields)

        Returns:
            JSON string with updated page data
        """
        try:
            # Build data dict with only provided values
            data = {}
            if title is not None:
                data["title"] = title
            if content is not None:
                data["content"] = content
            if status is not None:
                data["status"] = status
            if slug is not None:
                data["slug"] = slug
            if parent is not None:
                data["parent"] = parent
            if featured_media is not None:
                data["featured_media"] = featured_media
            if meta is not None:
                data["meta"] = meta

            page = await self.client.post(f"pages/{page_id}", json_data=data)

            result = {
                "id": page["id"],
                "title": page["title"]["rendered"],
                "status": page["status"],
                "link": page["link"],
                "message": f"Page {page_id} updated successfully",
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to update page {page_id}: {str(e)}"}, indent=2
            )

    async def delete_page(self, page_id: int, force: bool = False) -> str:
        """
        Delete or trash a WordPress page.

        Args:
            page_id: Page ID to delete
            force: Permanently delete (True) or move to trash (False)

        Returns:
            JSON string with deletion result
        """
        try:
            # Phase K.2.4: Convert boolean to string for WP REST API
            params = {"force": "true" if force else "false"}
            await self.client.delete(f"pages/{page_id}", params=params)

            if force:
                message = f"Page {page_id} permanently deleted"
            else:
                message = f"Page {page_id} moved to trash"

            result = {"id": page_id, "deleted": True, "force": force, "message": message}

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to delete page {page_id}: {str(e)}"}, indent=2
            )

    # === POST TYPES ===

    async def list_post_types(self) -> str:
        """
        List all registered post types.

        Returns list of all post types including built-in (post, page)
        and custom post types (portfolio, testimonials, etc.).

        Returns:
            JSON string with total count and post types list
        """
        try:
            types_data = await self.client.get("types")

            # Convert dict to list
            post_types = []
            if isinstance(types_data, dict):
                for slug, data in types_data.items():
                    post_types.append(
                        {
                            "slug": slug,
                            "name": data.get("name", slug),
                            "description": data.get("description", ""),
                            "rest_base": data.get("rest_base", slug),
                            "hierarchical": data.get("hierarchical", False),
                            "supports": data.get("supports", {}),
                        }
                    )

            result = {"total": len(post_types), "post_types": post_types}

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {"error": str(e), "message": f"Failed to list post types: {str(e)}"}, indent=2
            )

    async def get_post_type_info(self, post_type: str) -> str:
        """
        Get detailed information about a specific post type.

        Args:
            post_type: Post type slug (e.g., 'portfolio', 'post', 'page')

        Returns:
            JSON string with post type details
        """
        try:
            info = await self.client.get(f"types/{post_type}")
            return json.dumps(info, indent=2)
        except Exception as e:
            return json.dumps(
                {
                    "error": str(e),
                    "message": f"Failed to get post type info for '{post_type}': {str(e)}",
                },
                indent=2,
            )

    # === CUSTOM POSTS ===

    async def list_custom_posts(
        self, post_type: str, per_page: int = 10, page: int = 1, status: str = "any"
    ) -> str:
        """
        List posts of a specific custom post type.

        Args:
            post_type: Post type slug (e.g., 'portfolio', 'testimonials')
            per_page: Number of posts per page
            page: Page number
            status: Post status filter

        Returns:
            JSON string with posts list
        """
        try:
            params = {"per_page": per_page, "page": page, "status": status, "_embed": "true"}

            # Use the post type's rest_base as endpoint
            posts = await self.client.get(post_type, params=params)

            result = {
                "total": len(posts) if isinstance(posts, list) else 0,
                "post_type": post_type,
                "page": page,
                "per_page": per_page,
                "posts": posts,
            }

            return json.dumps(result, indent=2)
        except Exception as e:
            return json.dumps(
                {
                    "error": str(e),
                    "message": f"Failed to list custom posts of type '{post_type}': {str(e)}",
                },
                indent=2,
            )

    async def create_custom_post(
        self,
        post_type: str,
        title: str,
        content: str,
        status: str = "draft",
        meta: dict[str, Any] | None = None,
    ) -> str:
        """
        Create a new post of a custom post type.

        Args:
            post_type: Post type slug (e.g., 'portfolio')
            title: Post title
            content: Post content (HTML allowed)
            status: Post status (draft, publish, etc.)
            meta: Custom fields/meta data

        Returns:
            JSON string with created post details
        """
        try:
            data = {"title": title, "content": content, "status": status}

            if meta:
                data["meta"] = meta

            post = await self.client.post(post_type, json_data=data)

            return json.dumps(post, indent=2)
        except Exception as e:
            return json.dumps(
                {
                    "error": str(e),
                    "message": f"Failed to create custom post of type '{post_type}': {str(e)}",
                },
                indent=2,
            )

    # === INTERNAL LINKS ===

    async def get_internal_links(self, post_id: int) -> str:
        """
        Extract internal links from a post's content.

        Args:
            post_id: Post ID to analyze

        Returns:
            JSON string with internal and external link counts
        """
        try:
            post = await self.client.get(
                f"posts/{post_id}", params={"_fields": "id,title,content,link"}
            )

            content_html = post.get("content", {}).get("rendered", "")
            post_title = post.get("title", {}).get("rendered", "")
            post_link = post.get("link", "")

            # Extract site URL from post link
            from urllib.parse import urlparse

            parsed = urlparse(post_link)
            site_url = f"{parsed.scheme}://{parsed.netloc}" if parsed.scheme else ""

            # Extract all <a> tags with href and anchor text
            link_pattern = re.compile(
                r'<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)</a>',
                re.IGNORECASE | re.DOTALL,
            )
            matches = link_pattern.findall(content_html)

            internal_links = []
            external_count = 0

            for href, anchor_html in matches:
                anchor_text = re.sub(r"<[^>]+>", "", anchor_html).strip()
                href = href.strip()

                # Skip anchors and empty hrefs
                if not href or href.startswith("#") or href.startswith("mailto:"):
                    continue

                # Check if internal
                is_internal = False
                if site_url and href.startswith(site_url):
                    is_internal = True
                elif href.startswith("/") and not href.startswith("//"):
                    is_internal = True
                    href = site_url + href

                if is_internal:
                    internal_links.append(
                        {
                            "url": href,
                            "anchor_text": anchor_text,
                        }
                    )
                else:
                    external_count += 1

            result = {
                "post_id": post_id,
                "post_title": post_title,
                "site_url": site_url,
                "total_internal_links": len(internal_links),
                "internal_links": internal_links,
                "external_links_count": external_count,
            }

            return json.dumps(result, indent=2, ensure_ascii=False)
        except Exception as e:
            return json.dumps(
                {
                    "error": str(e),
                    "message": f"Failed to get internal links for post {post_id}: {str(e)}",
                },
                indent=2,
            )
