# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Reader Mode for Private Browser.

Extracts main article content from web pages,
removing navigation, ads, and clutter.
"""

import logging
import re
from dataclasses import dataclass
from typing import Dict, List, Optional
from urllib.parse import urlparse

logger = logging.getLogger(__name__)


@dataclass
class Article:
    """Extracted article content."""

    title: str
    author: Optional[str]
    date: Optional[str]
    content: str
    text: str
    word_count: int
    reading_time_minutes: int
    images: List[Dict[str, str]]
    site_name: Optional[str]


class ReaderMode:
    """
    Reader mode content extractor.

    Uses heuristics to find and extract the main
    article content from a web page, similar to
    Safari Reader or Firefox Reader View.
    """

    # Tags that typically contain article content
    CONTENT_TAGS = [
        "article",
        "main",
        '[role="main"]',
        ".post-content",
        ".article-content",
        ".entry-content",
        ".content",
        "#content",
        ".post",
        ".article",
        ".story",
        ".blog-post",
    ]

    # Tags to remove (navigation, sidebars, etc.)
    REMOVE_TAGS = [
        "nav",
        "header",
        "footer",
        "aside",
        ".sidebar",
        ".navigation",
        ".nav",
        ".menu",
        ".comments",
        ".comment",
        ".social",
        ".share",
        ".related",
        ".recommended",
        ".advertisement",
        ".ad",
        ".ads",
        "script",
        "style",
        "noscript",
        "iframe",
        "form",
    ]

    # Average reading speed (words per minute)
    WORDS_PER_MINUTE = 200

    async def extract(self, html: str, url: str) -> str:
        """
        Extract article content and return clean HTML.

        Args:
            html: Full page HTML
            url: Page URL

        Returns:
            Clean HTML for reader mode display
        """
        # Extract metadata
        title = self._extract_title(html)
        author = self._extract_author(html)
        date = self._extract_date(html)
        site_name = self._extract_site_name(html, url)

        # Clean HTML
        content = self._clean_html(html)

        # Find main content
        main_content = self._find_main_content(content)

        # Extract text (TODO: include images in reader-mode output)
        text = self._html_to_text(main_content)

        # Calculate reading time
        word_count = len(text.split())
        reading_time = max(1, word_count // self.WORDS_PER_MINUTE)

        # Build reader mode HTML
        reader_html = self._build_reader_html(
            title=title,
            author=author,
            date=date,
            site_name=site_name,
            content=main_content,
            reading_time=reading_time,
            word_count=word_count,
        )

        return reader_html

    def _extract_title(self, html: str) -> str:
        """Extract article title."""
        # Try og:title first
        match = re.search(
            r'<meta[^>]+property=["\']og:title["\'][^>]+content=["\']([^"\']+)["\']',
            html,
            re.IGNORECASE,
        )
        if match:
            return match.group(1)

        # Try h1
        match = re.search(r"<h1[^>]*>([^<]+)</h1>", html, re.IGNORECASE)
        if match:
            return match.group(1).strip()

        # Try title tag
        match = re.search(r"<title[^>]*>([^<]+)</title>", html, re.IGNORECASE)
        if match:
            title = match.group(1).strip()
            # Remove site name suffix
            if " | " in title:
                title = title.split(" | ")[0]
            elif " - " in title:
                title = title.split(" - ")[0]
            return title

        return "Untitled"

    def _extract_author(self, html: str) -> Optional[str]:
        """Extract article author."""
        # Try meta author
        match = re.search(
            r'<meta[^>]+name=["\']author["\'][^>]+content=["\']([^"\']+)["\']', html, re.IGNORECASE
        )
        if match:
            return match.group(1)

        # Try schema.org author
        match = re.search(r'"author"\s*:\s*{\s*"name"\s*:\s*"([^"]+)"', html)
        if match:
            return match.group(1)

        # Try common class patterns
        patterns = [
            r'class="[^"]*author[^"]*"[^>]*>([^<]+)<',
            r'class="[^"]*byline[^"]*"[^>]*>([^<]+)<',
            r'rel="author"[^>]*>([^<]+)<',
        ]

        for pattern in patterns:
            match = re.search(pattern, html, re.IGNORECASE)
            if match:
                author = match.group(1).strip()
                # Clean up
                author = re.sub(r"^by\s+", "", author, flags=re.IGNORECASE)
                if author and len(author) < 100:
                    return author

        return None

    def _extract_date(self, html: str) -> Optional[str]:
        """Extract publication date."""
        # Try meta tags
        meta_patterns = [
            r'<meta[^>]+property=["\']article:published_time["\'][^>]+content=["\']([^"\']+)["\']',
            r'<meta[^>]+name=["\']date["\'][^>]+content=["\']([^"\']+)["\']',
            r'<meta[^>]+name=["\']pubdate["\'][^>]+content=["\']([^"\']+)["\']',
        ]

        for pattern in meta_patterns:
            match = re.search(pattern, html, re.IGNORECASE)
            if match:
                return self._format_date(match.group(1))

        # Try time element
        match = re.search(r'<time[^>]+datetime=["\']([^"\']+)["\']', html)
        if match:
            return self._format_date(match.group(1))

        return None

    def _format_date(self, date_str: str) -> str:
        """Format date string nicely."""
        try:
            from datetime import datetime

            # Try ISO format
            if "T" in date_str:
                dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
                return dt.strftime("%B %d, %Y")

            # Try common formats
            for fmt in ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y"]:
                try:
                    dt = datetime.strptime(date_str[:10], fmt)
                    return dt.strftime("%B %d, %Y")
                except ValueError as e:
                    logger.debug("Value conversion error suppressed: %s", e)
                    continue
            return date_str
        except Exception:
            return date_str

    def _extract_site_name(self, html: str, url: str) -> Optional[str]:
        """Extract site name."""
        # Try og:site_name
        match = re.search(
            r'<meta[^>]+property=["\']og:site_name["\'][^>]+content=["\']([^"\']+)["\']',
            html,
            re.IGNORECASE,
        )
        if match:
            return match.group(1)

        # Use domain
        parsed = urlparse(url)
        domain = parsed.netloc
        if domain.startswith("www."):
            domain = domain[4:]

        return domain

    def _clean_html(self, html: str) -> str:
        """Remove unwanted elements from HTML."""
        # Remove scripts and styles
        html = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.IGNORECASE)
        html = re.sub(r"<style[^>]*>.*?</style>", "", html, flags=re.DOTALL | re.IGNORECASE)
        html = re.sub(r"<noscript[^>]*>.*?</noscript>", "", html, flags=re.DOTALL | re.IGNORECASE)

        # Remove comments
        html = re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL)

        # Remove navigation, sidebars, etc.
        remove_patterns = [
            r"<nav[^>]*>.*?</nav>",
            r"<header[^>]*>.*?</header>",
            r"<footer[^>]*>.*?</footer>",
            r"<aside[^>]*>.*?</aside>",
            r'<div[^>]+class="[^"]*(?:sidebar|navigation|menu|comments|social|share|related|ad|ads|advertisement)[^"]*"[^>]*>.*?</div>',
        ]

        for pattern in remove_patterns:
            html = re.sub(pattern, "", html, flags=re.DOTALL | re.IGNORECASE)

        return html

    def _find_main_content(self, html: str) -> str:
        """Find the main article content."""
        # Try to find article element
        match = re.search(r"<article[^>]*>(.*?)</article>", html, re.DOTALL | re.IGNORECASE)
        if match:
            return self._clean_content(match.group(1))

        # Try main element
        match = re.search(r"<main[^>]*>(.*?)</main>", html, re.DOTALL | re.IGNORECASE)
        if match:
            return self._clean_content(match.group(1))

        # Try common content class patterns
        content_patterns = [
            r'<div[^>]+class="[^"]*(?:post-content|article-content|entry-content|content-body)[^"]*"[^>]*>(.*?)</div>',
            r'<div[^>]+class="[^"]*(?:post|article|story)[^"]*"[^>]*>(.*?)</div>',
            r'<div[^>]+id="(?:content|main|article)"[^>]*>(.*?)</div>',
        ]

        for pattern in content_patterns:
            match = re.search(pattern, html, re.DOTALL | re.IGNORECASE)
            if match:
                content = match.group(1)
                # Check if it has enough text
                text = re.sub(r"<[^>]+>", "", content)
                if len(text.split()) > 100:
                    return self._clean_content(content)

        # Fallback: find the div with most text
        best_content = ""
        best_length = 0

        for match in re.finditer(r"<div[^>]*>(.*?)</div>", html, re.DOTALL | re.IGNORECASE):
            content = match.group(1)
            text = re.sub(r"<[^>]+>", "", content)
            word_count = len(text.split())

            if word_count > best_length and word_count > 100:
                best_length = word_count
                best_content = content

        if best_content:
            return self._clean_content(best_content)

        # Last resort: return body
        match = re.search(r"<body[^>]*>(.*?)</body>", html, re.DOTALL | re.IGNORECASE)
        if match:
            return self._clean_content(match.group(1))

        return html

    def _clean_content(self, content: str) -> str:
        """Clean extracted content using allowlist sanitization."""
        from familiar.core.sanitization import sanitize_html

        return sanitize_html(content)

    def _html_to_text(self, html: str) -> str:
        """Convert HTML to plain text."""
        # Remove tags
        text = re.sub(r"<[^>]+>", " ", html)

        # Decode entities
        text = text.replace("&nbsp;", " ")
        text = text.replace("&amp;", "&")
        text = text.replace("&lt;", "<")
        text = text.replace("&gt;", ">")
        text = text.replace("&quot;", '"')
        text = text.replace("&#39;", "'")

        # Normalize whitespace
        text = re.sub(r"\s+", " ", text)

        return text.strip()

    def _extract_images(self, html: str) -> List[Dict[str, str]]:
        """Extract images from content."""
        images = []

        for match in re.finditer(r'<img[^>]+src=["\']([^"\']+)["\']', html, re.IGNORECASE):
            src = match.group(1)

            # Get alt text
            alt_match = re.search(r'alt=["\']([^"\']*)["\']', match.group(0))
            alt = alt_match.group(1) if alt_match else ""

            images.append(
                {
                    "src": src,
                    "alt": alt,
                }
            )

        return images

    def _build_reader_html(
        self,
        title: str,
        author: Optional[str],
        date: Optional[str],
        site_name: Optional[str],
        content: str,
        reading_time: int,
        word_count: int,
    ) -> str:
        """Build the reader mode HTML document."""

        # Metadata line
        meta_parts = []
        if author:
            meta_parts.append(f"By {author}")
        if date:
            meta_parts.append(date)
        if site_name:
            meta_parts.append(site_name)
        meta_parts.append(f"{reading_time} min read")

        meta_line = " · ".join(meta_parts)

        from html import escape as _esc

        title = _esc(title)
        meta_line = _esc(meta_line)

        return f"""<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{title}</title>
    <style>
        :root {{
            --bg: #fafafa;
            --text: #1a1a1a;
            --meta: #666;
            --link: #0066cc;
            --border: #e0e0e0;
            --quote-bg: #f5f5f5;
        }}

        @media (prefers-color-scheme: dark) {{
            :root {{
                --bg: #1a1a1a;
                --text: #e0e0e0;
                --meta: #999;
                --link: #6699ff;
                --border: #333;
                --quote-bg: #252525;
            }}
        }}

        * {{
            box-sizing: border-box;
        }}

        body {{
            font-family: Georgia, 'Times New Roman', serif;
            font-size: 20px;
            line-height: 1.7;
            color: var(--text);
            background: var(--bg);
            margin: 0;
            padding: 24px;
            max-width: 680px;
            margin: 0 auto;
        }}

        h1 {{
            font-size: 2em;
            line-height: 1.2;
            margin: 0 0 16px 0;
            font-weight: 700;
        }}

        .meta {{
            color: var(--meta);
            font-size: 0.85em;
            margin-bottom: 32px;
            padding-bottom: 16px;
            border-bottom: 1px solid var(--border);
        }}

        h2, h3, h4 {{
            font-weight: 600;
            margin-top: 32px;
        }}

        p {{
            margin: 0 0 24px 0;
        }}

        a {{
            color: var(--link);
        }}

        img {{
            max-width: 100%;
            height: auto;
            display: block;
            margin: 24px 0;
            border-radius: 4px;
        }}

        blockquote {{
            margin: 24px 0;
            padding: 16px 24px;
            background: var(--quote-bg);
            border-left: 4px solid var(--link);
            font-style: italic;
        }}

        pre, code {{
            font-family: 'SF Mono', Menlo, monospace;
            font-size: 0.85em;
            background: var(--quote-bg);
            border-radius: 4px;
        }}

        pre {{
            padding: 16px;
            overflow-x: auto;
        }}

        code {{
            padding: 2px 6px;
        }}

        pre code {{
            padding: 0;
            background: none;
        }}

        ul, ol {{
            margin: 0 0 24px 0;
            padding-left: 24px;
        }}

        li {{
            margin-bottom: 8px;
        }}

        hr {{
            border: none;
            border-top: 1px solid var(--border);
            margin: 48px 0;
        }}

        figure {{
            margin: 24px 0;
        }}

        figcaption {{
            color: var(--meta);
            font-size: 0.85em;
            text-align: center;
            margin-top: 8px;
        }}
    </style>
</head>
<body>
    <article>
        <h1>{title}</h1>
        <div class="meta">{meta_line}</div>
        <div class="content">
            {content}
        </div>
    </article>
</body>
</html>"""
