"""Type definitions for the Undetecta API client.

These types are derived from the API's Zod schemas and match the JavaScript SDK.
"""

from __future__ import annotations

from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class ScrapeFormat(str, Enum):
    """Scrape format options."""

    HTML = "html"
    RAW_HTML = "rawHtml"
    MARKDOWN = "markdown"
    LINKS = "links"
    SCREENSHOT = "screenshot"
    BRANDING = "branding"


class WaitUntil(str, Enum):
    """Wait until options for page load."""

    LOAD = "load"
    DOM_CONTENT_LOADED = "domcontentloaded"
    NETWORK_IDLE = "networkidle"


class JobStatus(str, Enum):
    """Job status."""

    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    STOPPED = "stopped"


class ActionType(str, Enum):
    """Browser action type."""

    CLICK = "click"
    FILL = "fill"
    SELECT = "select"
    WAIT = "wait"
    SCROLL = "scroll"


class SearchSource(str, Enum):
    """Search source type."""

    WEB = "web"
    NEWS = "news"
    IMAGES = "images"


class SearchEngine(str, Enum):
    """Search engine type."""

    GOOGLE = "google"
    DUCK_DUCK_GO = "duckduckgo"


class SearchCategory(str, Enum):
    """Search category type."""

    GITHUB = "github"
    RESEARCH = "research"
    PDF = "pdf"


class ScreenshotClip(BaseModel):
    """Screenshot clip viewport definition."""

    x: int
    y: int
    width: int
    height: int
    scale: float | None = None


class ScreenshotViewport(BaseModel):
    """Screenshot viewport definition."""

    width: int
    height: int
    device_scale_factor: float | None = Field(None, alias="deviceScaleFactor")


class ScreenshotOptions(BaseModel):
    """Screenshot options."""

    full_page: bool | None = Field(None, alias="fullPage")
    format: str | None = "png"
    quality: int | None = None
    clip: ScreenshotClip | None = None
    selector: str | None = None
    viewport: ScreenshotViewport | None = None
    optimize_for_speed: bool | None = Field(None, alias="optimizeForSpeed")


class Action(BaseModel):
    """Browser action."""

    type: ActionType
    selector: str | None = None
    value: str | None = None
    options: dict[str, Any] | None = None


class ScrapeMetadata(BaseModel):
    """Scrape metadata returned by the API."""

    status_code: int | None = Field(None, alias="statusCode")
    url: str | None = None
    error: str | None = None
    title: str | None = None
    description: str | None = None
    language: str | None = None
    keywords: str | None = None
    author: str | None = None
    robots: str | None = None
    canonical: str | None = None
    favicon: str | None = None
    og_title: str | None = Field(None, alias="ogTitle")
    og_description: str | None = Field(None, alias="ogDescription")
    og_url: str | None = Field(None, alias="ogUrl")
    og_image: str | None = Field(None, alias="ogImage")
    og_type: str | None = Field(None, alias="ogType")
    og_site_name: str | None = Field(None, alias="ogSiteName")
    og_locale: str | None = Field(None, alias="ogLocale")
    og_locale_alternate: list[str] | None = Field(None, alias="ogLocaleAlternate")
    twitter_card: str | None = Field(None, alias="twitterCard")
    twitter_site: str | None = Field(None, alias="twitterSite")
    twitter_creator: str | None = Field(None, alias="twitterCreator")
    twitter_title: str | None = Field(None, alias="twitterTitle")
    twitter_description: str | None = Field(None, alias="twitterDescription")
    twitter_image: str | None = Field(None, alias="twitterImage")

    model_config = {"populate_by_name": True}


class BrandingColors(BaseModel):
    """Branding colors extracted from a webpage."""

    primary: str | None = None
    secondary: str | None = None
    accent: str | None = None
    background: str | None = None
    text_primary: str | None = Field(None, alias="textPrimary")
    text_secondary: str | None = Field(None, alias="textSecondary")
    link: str | None = None

    model_config = {"populate_by_name": True}


class BrandingTypography(BaseModel):
    """Branding typography information."""

    font_families: dict[str, str] | None = Field(None, alias="fontFamilies")
    font_stacks: dict[str, list[str]] | None = Field(None, alias="fontStacks")
    font_sizes: dict[str, str] | None = Field(None, alias="fontSizes")
    font_weights: dict[str, str] | None = Field(None, alias="fontWeights")
    line_heights: dict[str, str] | None = Field(None, alias="lineHeights")

    model_config = {"populate_by_name": True}


class BrandingSpacing(BaseModel):
    """Branding spacing information."""

    base_unit: int | None = Field(None, alias="baseUnit")
    border_radius: str | None = Field(None, alias="borderRadius")
    padding: dict[str, str] | None = None
    margins: dict[str, str] | None = None

    model_config = {"populate_by_name": True}


class BrandingButtonStyle(BaseModel):
    """Branding button style."""

    background_color: str | None = Field(None, alias="backgroundColor")
    text_color: str | None = Field(None, alias="textColor")
    border_radius: str | None = Field(None, alias="borderRadius")
    font_size: str | None = Field(None, alias="fontSize")
    font_weight: str | None = Field(None, alias="fontWeight")
    padding: str | None = None

    model_config = {"populate_by_name": True}


class BrandingComponents(BaseModel):
    """Branding components."""

    button_primary: BrandingButtonStyle | None = Field(None, alias="buttonPrimary")
    button_secondary: BrandingButtonStyle | None = Field(None, alias="buttonSecondary")
    input: dict[str, str] | None = None

    model_config = {"populate_by_name": True}


class BrandingImages(BaseModel):
    """Branding images."""

    logo: str | None = None
    favicon: str | None = None
    og_image: str | None = Field(None, alias="ogImage")

    model_config = {"populate_by_name": True}


class BrandingConfidence(BaseModel):
    """Branding confidence scores."""

    buttons: float
    colors: float
    overall: float


class BrandingPersonality(BaseModel):
    """Branding personality."""

    tone: str | None = None
    energy: str | None = None
    target_audience: str | None = Field(None, alias="targetAudience")

    model_config = {"populate_by_name": True}


class BrandingProfile(BaseModel):
    """Branding profile extracted from a webpage."""

    color_scheme: str | None = Field(None, alias="colorScheme")
    colors: BrandingColors | None = None
    typography: BrandingTypography | None = None
    spacing: BrandingSpacing | None = None
    components: BrandingComponents | None = None
    images: BrandingImages | None = None
    social_links: list[str] | None = Field(None, alias="socialLinks")
    framework_hints: list[str] | None = Field(None, alias="__framework_hints")
    confidence: BrandingConfidence | None = None
    personality: BrandingPersonality | None = None

    model_config = {"populate_by_name": True}


class ScrapeJobError(BaseModel):
    """Scrape job error."""

    code: str
    message: str


class ScrapeJobResponse(BaseModel):
    """Scrape job response."""

    id: str
    status: JobStatus
    created_at: str = Field(..., alias="createdAt")
    completed_at: str | None = Field(None, alias="completedAt")
    metadata: ScrapeMetadata | None = None
    html: str | None = None
    raw_html: str | None = Field(None, alias="rawHtml")
    markdown: str | None = None
    links: list[str] | None = None
    screenshot: str | None = None
    branding: BrandingProfile | None = None
    actions: Any | None = None
    warning: str | None = None
    error: ScrapeJobError | None = None

    model_config = {"populate_by_name": True}


class ScrapeOptions(BaseModel):
    """Scrape request options."""

    # Required
    url: str

    # Browser/Session options
    headless: bool | None = None
    proxy: str | None = None
    adblock: bool | None = None
    trackers: bool | None = None
    annoyances: bool | None = None
    block_images: bool | None = Field(None, alias="blockImages")
    block_stylesheets: bool | None = Field(None, alias="blockStylesheets")
    block_fonts: bool | None = Field(None, alias="blockFonts")
    block_media: bool | None = Field(None, alias="blockMedia")

    # Scrape behavior options
    formats: list[ScrapeFormat] | None = None
    container_selector: str | None = Field(None, alias="containerSelector")
    include_tags: list[str] | None = Field(None, alias="includeTags")
    exclude_tags: list[str] | None = Field(None, alias="excludeTags")
    only_main_content: bool | None = Field(None, alias="onlyMainContent")
    wait_for: int | None = Field(None, alias="waitFor")
    wait_until: WaitUntil | None = Field(None, alias="waitUntil")
    timeout: int | None = None
    wait_for_selector: str | None = Field(None, alias="waitForSelector")
    wait_for_text: str | None = Field(None, alias="waitForText")
    wait_for_timeout: int | None = Field(None, alias="waitForTimeout")
    headers: dict[str, str] | None = None
    mobile: bool | None = None
    skip_tls_verification: bool | None = Field(None, alias="skipTlsVerification")
    remove_base64_images: bool | None = Field(None, alias="removeBase64Images")
    optimize_images: bool | None = Field(None, alias="optimizeImages")
    screenshot_options: ScreenshotOptions | None = Field(None, alias="screenshotOptions")
    actions: list[Action] | None = None

    model_config = {"populate_by_name": True}


class ScrapedMetadata(BaseModel):
    """Scraped page metadata for search results."""

    title: str | None = None
    description: str | None = None
    source_url: str | None = Field(None, alias="sourceURL")
    status_code: int | None = Field(None, alias="statusCode")
    error: str | None = None
    language: str | None = None
    keywords: str | None = None
    author: str | None = None
    robots: str | None = None
    canonical: str | None = None
    favicon: str | None = None
    og_title: str | None = Field(None, alias="ogTitle")
    og_description: str | None = Field(None, alias="ogDescription")
    og_url: str | None = Field(None, alias="ogUrl")
    og_image: str | None = Field(None, alias="ogImage")
    og_type: str | None = Field(None, alias="ogType")
    og_site_name: str | None = Field(None, alias="ogSiteName")
    og_locale: str | None = Field(None, alias="ogLocale")
    og_locale_alternate: list[str] | None = Field(None, alias="ogLocaleAlternate")
    twitter_card: str | None = Field(None, alias="twitterCard")
    twitter_site: str | None = Field(None, alias="twitterSite")
    twitter_creator: str | None = Field(None, alias="twitterCreator")
    twitter_title: str | None = Field(None, alias="twitterTitle")
    twitter_description: str | None = Field(None, alias="twitterDescription")
    twitter_image: str | None = Field(None, alias="twitterImage")

    model_config = {"populate_by_name": True}


class WebSearchResult(BaseModel):
    """Web search result."""

    url: str
    title: str
    description: str
    position: int | None = None
    category: str | None = None
    # Scraped content (when scrapeOptions provided)
    markdown: str | None = None
    html: str | None = None
    raw_html: str | None = Field(None, alias="rawHtml")
    links: list[str] | None = None
    screenshot: str | None = None
    metadata: ScrapedMetadata | None = None

    model_config = {"populate_by_name": True}


class NewsSearchResult(BaseModel):
    """News search result."""

    url: str | None = None
    title: str | None = None
    snippet: str | None = None
    date: str | None = None
    image_url: str | None = Field(None, alias="imageUrl")
    position: int | None = None
    category: str | None = None
    # Scraped content
    markdown: str | None = None
    html: str | None = None
    raw_html: str | None = Field(None, alias="rawHtml")
    links: list[str] | None = None
    screenshot: str | None = None
    metadata: ScrapedMetadata | None = None

    model_config = {"populate_by_name": True}


class ImageSearchResult(BaseModel):
    """Image search result."""

    image_url: str = Field(..., alias="imageUrl")
    image_width: int | None = Field(None, alias="imageWidth")
    image_height: int | None = Field(None, alias="imageHeight")
    url: str | None = None
    title: str | None = None
    position: int

    model_config = {"populate_by_name": True}


class SearchJobError(BaseModel):
    """Search job error."""

    code: str
    message: str


class SearchJobResponse(BaseModel):
    """Search job response."""

    id: str
    status: JobStatus
    created_at: str = Field(..., alias="createdAt")
    completed_at: str | None = Field(None, alias="completedAt")
    web: list[WebSearchResult] | None = None
    news: list[NewsSearchResult] | None = None
    images: list[ImageSearchResult] | None = None
    warning: str | None = None
    error: SearchJobError | None = None

    model_config = {"populate_by_name": True}


class SearchScrapeOptions(BaseModel):
    """Search scrape options for result scraping."""

    formats: list[ScrapeFormat] | None = None
    include_tags: list[str] | None = Field(None, alias="includeTags")
    exclude_tags: list[str] | None = Field(None, alias="excludeTags")
    only_main_content: bool | None = Field(None, alias="onlyMainContent")
    wait_for: int | None = Field(None, alias="waitFor")
    wait_until: WaitUntil | None = Field(None, alias="waitUntil")
    timeout: int | None = None
    wait_for_selector: str | None = Field(None, alias="waitForSelector")
    wait_for_text: str | None = Field(None, alias="waitForText")
    wait_for_timeout: int | None = Field(None, alias="waitForTimeout")
    headers: dict[str, str] | None = None
    mobile: bool | None = None
    skip_tls_verification: bool | None = Field(None, alias="skipTlsVerification")
    remove_base64_images: bool | None = Field(None, alias="removeBase64Images")
    optimize_images: bool | None = Field(None, alias="optimizeImages")
    screenshot_options: ScreenshotOptions | None = Field(None, alias="screenshotOptions")

    model_config = {"populate_by_name": True}


class SearchOptions(BaseModel):
    """Search request options."""

    # Required
    query: str

    # Search engine selection
    engine: SearchEngine | None = None

    # Pagination
    limit: int | None = None

    # Sources to search
    sources: list[SearchSource] | None = None

    # Category filters
    categories: list[SearchCategory] | None = None

    # Localization
    lang: str | None = None
    country: str | None = None
    location: str | None = None

    # Time-based filtering
    tbs: str | None = None

    # Timeout
    timeout: int | None = None

    # Scraping options
    scrape_options: SearchScrapeOptions | None = Field(None, alias="scrapeOptions")

    # Skip URLs that fail validation
    ignore_invalid_urls: bool | None = Field(None, alias="ignoreInvalidURLs")

    model_config = {"populate_by_name": True}


class HealthResponse(BaseModel):
    """Health check response."""

    status: str
    timestamp: str
