# 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 for clean reading.
Similar to Safari Reader or Firefox Reader View.
"""

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

logger = logging.getLogger(__name__)


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

    title: str
    author: Optional[str]
    date: Optional[str]
    content_html: str
    content_text: str
    word_count: int
    reading_time_minutes: int
    images: List[Dict[str, str]]


# Elements that typically contain main content
CONTENT_TAGS = ["article", "main", "section"]
CONTENT_CLASSES = [
    "article",
    "post",
    "content",
    "entry",
    "story",
    "article-content",
    "post-content",
    "entry-content",
    "article-body",
    "post-body",
    "story-body",
    "main-content",
    "page-content",
]
CONTENT_IDS = [
    "article",
    "content",
    "main",
    "post",
    "story",
    "article-content",
    "main-content",
    "page-content",
]

# Elements to remove
REMOVE_TAGS = [
    "script",
    "style",
    "nav",
    "header",
    "footer",
    "aside",
    "form",
    "button",
    "input",
    "select",
    "textarea",
    "iframe",
    "object",
    "embed",
    "video",
    "audio",
    "noscript",
    "canvas",
    "svg",
]
REMOVE_CLASSES = [
    "nav",
    "menu",
    "sidebar",
    "widget",
    "ad",
    "ads",
    "advert",
    "social",
    "share",
    "sharing",
    "comment",
    "comments",
    "related",
    "recommended",
    "popular",
    "trending",
    "footer",
    "header",
    "banner",
    "promo",
    "newsletter",
    "subscribe",
    "signup",
    "login",
    "modal",
    "popup",
]


class ReaderMode:
    """
    Reader mode content extractor.

    Extracts the main readable content from web pages,
    removing navigation, ads, and other distractions.
    """

    def __init__(self):
        self.min_content_length = 200  # Minimum chars for valid content
        self.min_paragraph_length = 50

    async def extract(
        self,
        html: str,
        url: str,
    ) -> str:
        """
        Extract readable content from HTML.

        Args:
            html: Raw HTML content
            url: Page URL for resolving relative URLs

        Returns:
            Clean HTML suitable for reading
        """
        # Extract title
        title = self._extract_title(html)

        # Extract metadata
        author = self._extract_author(html)
        date = self._extract_date(html)

        # Remove unwanted elements
        cleaned = self._remove_unwanted(html)

        # Find main content
        content = self._find_content(cleaned)

        if not content or len(content) < self.min_content_length:
            # Fallback: extract all paragraphs
            content = self._extract_paragraphs(cleaned)

        # Clean up the content
        content = self._clean_content(content, url)

        # Extract images from content
        # TODO: include images in reader-mode output

        # Calculate reading time
        text = self._html_to_text(content)
        word_count = len(text.split())
        reading_time = max(1, word_count // 200)  # Assume 200 WPM

        # Build reader view HTML
        reader_html = self._build_reader_html(
            title=title,
            author=author,
            date=date,
            content=content,
            url=url,
            word_count=word_count,
            reading_time=reading_time,
        )

        return reader_html

    def _extract_title(self, html: str) -> str:
        """Extract article title."""
        # Try meta og:title
        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
            title = re.sub(r"\s*[\|—–-]\s*[^|—–-]+$", "", title)
            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"[^}]*"name"\s*:\s*"([^"]+)"', html)
        if match:
            return match.group(1)

        # Try common author patterns
        patterns = [
            r'class=["\'][^"\']*author[^"\']*["\'][^>]*>([^<]+)<',
            r'rel=["\']author["\'][^>]*>([^<]+)<',
            r"by\s+<[^>]+>([^<]+)<",
        ]
        for pattern in patterns:
            match = re.search(pattern, html, re.IGNORECASE)
            if match:
                return match.group(1).strip()

        return None

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

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

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

        return None

    def _remove_unwanted(self, html: str) -> str:
        """Remove unwanted elements."""
        # Remove specified tags
        for tag in REMOVE_TAGS:
            html = re.sub(rf"<{tag}[^>]*>.*?</{tag}>", "", html, flags=re.IGNORECASE | re.DOTALL)
            # Self-closing
            html = re.sub(rf"<{tag}[^>]*/>", "", html, flags=re.IGNORECASE)

        # Remove elements with unwanted classes
        for cls in REMOVE_CLASSES:
            html = re.sub(
                rf'<[^>]+class=["\'][^"\']*\b{cls}\b[^"\']*["\'][^>]*>.*?</[^>]+>',  # noqa: F541
                "",
                html,
                flags=re.IGNORECASE | re.DOTALL,
            )

        # Remove hidden elements
        html = re.sub(
            r"<[^>]+(?:display:\s*none|visibility:\s*hidden)[^>]*>.*?</[^>]+>",
            "",
            html,
            flags=re.IGNORECASE | re.DOTALL,
        )

        return html

    def _find_content(self, html: str) -> Optional[str]:
        """Find the main content container."""
        best_content = None
        best_score = 0

        # Try article tags
        for tag in CONTENT_TAGS:
            matches = re.findall(rf"<{tag}[^>]*>(.*?)</{tag}>", html, re.IGNORECASE | re.DOTALL)
            for match in matches:
                score = self._score_content(match)
                if score > best_score:
                    best_score = score
                    best_content = match

        # Try content IDs
        for id_name in CONTENT_IDS:
            match = re.search(
                rf'<[^>]+id=["\']' + id_name + rf'["\'][^>]*>(.*?)</[^>]+>',  # noqa: F541
                html,
                re.IGNORECASE | re.DOTALL,
            )
            if match:
                score = self._score_content(match.group(1))
                if score > best_score:
                    best_score = score
                    best_content = match.group(1)

        # Try content classes
        for cls in CONTENT_CLASSES:
            match = re.search(
                rf'<[^>]+class=["\'][^"\']*\b' + cls + rf'\b[^"\']*["\'][^>]*>(.*?)</[^>]+>',  # noqa: F541
                html,
                re.IGNORECASE | re.DOTALL,
            )
            if match:
                score = self._score_content(match.group(1))
                if score > best_score:
                    best_score = score
                    best_content = match.group(1)

        return best_content

    def _score_content(self, html: str) -> int:
        """Score content quality."""
        score = 0

        # Count paragraphs
        paragraphs = re.findall(r"<p[^>]*>(.+?)</p>", html, re.DOTALL | re.IGNORECASE)
        long_paragraphs = [p for p in paragraphs if len(p) > self.min_paragraph_length]
        score += len(long_paragraphs) * 10

        # Bonus for headings
        score += len(re.findall(r"<h[2-6][^>]*>", html, re.IGNORECASE)) * 5

        # Bonus for lists
        score += len(re.findall(r"<(?:ul|ol)[^>]*>", html, re.IGNORECASE)) * 3

        # Penalty for links (likely navigation)
        score -= len(re.findall(r"<a[^>]*>", html, re.IGNORECASE))

        # Text length bonus
        text = self._html_to_text(html)
        score += len(text) // 100

        return max(0, score)

    def _extract_paragraphs(self, html: str) -> str:
        """Extract all paragraphs as fallback."""
        paragraphs = re.findall(r"<p[^>]*>(.+?)</p>", html, re.DOTALL | re.IGNORECASE)

        # Filter short paragraphs
        good_paragraphs = [
            p for p in paragraphs if len(self._html_to_text(p)) > self.min_paragraph_length
        ]

        return "\n".join(f"<p>{p}</p>" for p in good_paragraphs)

    def _clean_content(self, html: str, base_url: str) -> str:
        """Clean and normalize content."""
        # Remove inline styles
        html = re.sub(r'\s+style=["\'][^"\']*["\']', "", html)

        # Remove class attributes (keep id for linking)
        html = re.sub(r'\s+class=["\'][^"\']*["\']', "", html)

        # Remove onclick and other event handlers
        html = re.sub(r'\s+on\w+=["\'][^"\']*["\']', "", html)

        # Make image URLs absolute
        html = re.sub(
            r'src=["\']([^"\']+)["\']', lambda m: f'src="{urljoin(base_url, m.group(1))}"', html
        )

        # Make link URLs absolute
        html = re.sub(
            r'href=["\']([^"\']+)["\']',
            lambda m: (
                f'href="{urljoin(base_url, m.group(1))}"'
                if not m.group(1).startswith(("#", "mailto:", "tel:", "javascript:"))
                else m.group(0)
            ),
            html,
        )

        return html

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

        for match in re.finditer(r"<img[^>]+>", html, re.IGNORECASE):
            tag = match.group(0)

            src_match = re.search(r'src=["\']([^"\']+)["\']', tag)
            if not src_match:
                continue

            src = urljoin(base_url, src_match.group(1))

            alt_match = re.search(r'alt=["\']([^"\']*)["\']', tag)
            alt = alt_match.group(1) if alt_match else ""

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

        return images

    def _html_to_text(self, html: str) -> str:
        """Convert HTML to plain text."""
        text = re.sub(r"<[^>]+>", " ", html)
        text = re.sub(r"\s+", " ", text)
        text = text.replace("&nbsp;", " ")
        text = text.replace("&amp;", "&")
        text = text.replace("&lt;", "<")
        text = text.replace("&gt;", ">")
        return text.strip()

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

        # Format date if present
        date_html = ""
        if date:
            try:
                from datetime import datetime

                dt = datetime.fromisoformat(date.replace("Z", "+00:00"))
                date_html = f'<time datetime="{date}">{dt.strftime("%B %d, %Y")}</time>'
            except Exception:
                date_html = f"<time>{date}</time>"

        # Build metadata line
        meta_parts = []
        if author:
            meta_parts.append(f'<span class="author">By {author}</span>')
        if date_html:
            meta_parts.append(date_html)
        meta_parts.append(f'<span class="reading-time">{reading_time} min read</span>')
        meta_parts.append(f'<span class="word-count">{word_count:,} words</span>')

        meta_html = " · ".join(meta_parts)

        from html import escape as _esc

        title = _esc(title)
        meta_html = _esc(meta_html)
        url = _esc(url)

        return f'''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title}</title>
    <style>
        :root {{
            --bg: #fefefe;
            --text: #1a1a1a;
            --text-secondary: #666;
            --link: #0066cc;
            --border: #e0e0e0;
            --max-width: 680px;
            --font-body: Georgia, 'Times New Roman', serif;
            --font-heading: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        }}

        @media (prefers-color-scheme: dark) {{
            :root {{
                --bg: #1a1a1a;
                --text: #e0e0e0;
                --text-secondary: #999;
                --link: #6db3f2;
                --border: #333;
            }}
        }}

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

        body {{
            font-family: var(--font-body);
            font-size: 20px;
            line-height: 1.7;
            color: var(--text);
            background: var(--bg);
            margin: 0;
            padding: 24px;
        }}

        .reader-container {{
            max-width: var(--max-width);
            margin: 0 auto;
        }}

        .reader-header {{
            margin-bottom: 32px;
            padding-bottom: 24px;
            border-bottom: 1px solid var(--border);
        }}

        h1 {{
            font-family: var(--font-heading);
            font-size: 2em;
            line-height: 1.2;
            margin: 0 0 16px 0;
        }}

        .meta {{
            font-size: 0.8em;
            color: var(--text-secondary);
        }}

        .meta a {{
            color: var(--text-secondary);
        }}

        .reader-content {{
            font-size: 1em;
        }}

        .reader-content p {{
            margin: 0 0 1.5em 0;
        }}

        .reader-content h2,
        .reader-content h3,
        .reader-content h4 {{
            font-family: var(--font-heading);
            margin: 2em 0 0.5em 0;
        }}

        .reader-content a {{
            color: var(--link);
        }}

        .reader-content img {{
            max-width: 100%;
            height: auto;
            display: block;
            margin: 1.5em 0;
        }}

        .reader-content blockquote {{
            margin: 1.5em 0;
            padding-left: 1em;
            border-left: 3px solid var(--border);
            color: var(--text-secondary);
        }}

        .reader-content pre,
        .reader-content code {{
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 0.85em;
            background: rgba(0,0,0,0.05);
            border-radius: 4px;
        }}

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

        .reader-content code {{
            padding: 2px 6px;
        }}

        .reader-content pre code {{
            padding: 0;
            background: none;
        }}

        .reader-footer {{
            margin-top: 48px;
            padding-top: 24px;
            border-top: 1px solid var(--border);
            font-size: 0.8em;
            color: var(--text-secondary);
        }}

        .reader-footer a {{
            color: var(--link);
        }}

        @media (max-width: 600px) {{
            body {{
                font-size: 18px;
                padding: 16px;
            }}

            h1 {{
                font-size: 1.5em;
            }}
        }}
    </style>
</head>
<body>
    <div class="reader-container">
        <header class="reader-header">
            <h1>{title}</h1>
            <div class="meta">{meta_html}</div>
        </header>

        <article class="reader-content">
            {content}
        </article>

        <footer class="reader-footer">
            <p>Original article: <a href="{url}" target="_blank">{url}</a></p>
        </footer>
    </div>
</body>
</html>'''
