package com.correx.infrastructure.tools.extract

import org.jsoup.nodes.Document
import org.jsoup.nodes.Element

/**
 * Structural strip (§5.1) + density extraction (§5.2). Removes boilerplate by tag and by
 * class/id blocklist, then picks the main-content subtree by a text-density / link-density score
 * (readability/trafilatura family). Pure DOM transformation — no inference, no network.
 */
internal object MainContentSelector {

    /** Whole-tag boilerplate removed outright. */
    private val STRIP_TAGS = listOf(
        "script", "style", "noscript", "template", "nav", "footer", "header",
        "aside", "iframe", "form", "svg", "button", "input", "select", "textarea",
    )

    /** Substrings matched (case-insensitive) against class/id; a node carrying one is chrome, not content. */
    private val BLOCKLIST = listOf(
        "cookie", "banner", "sidebar", "related", "share", "comment", "newsletter",
        "promo", "ad-", "-ad", "advert", "social", "popup", "modal", "subscribe",
        "breadcrumb", "pagination", "menu", "nav-", "footer", "header",
    )

    /** Containers that, when present and substantial, are the main content without scoring. */
    private val SEMANTIC_MAIN = listOf("article", "main", "[role=main]")

    /** Strips boilerplate in place, then returns the best main-content element (falls back to body). */
    fun select(document: Document): Element {
        strip(document)
        val semantic = SEMANTIC_MAIN.firstNotNullOfOrNull { sel ->
            document.select(sel).maxByOrNull { textLength(it) }?.takeIf { textLength(it) > 0 }
        }
        if (semantic != null) return semantic
        return bestByDensity(document) ?: document.body() ?: document
    }

    private fun strip(document: Document) {
        document.select(STRIP_TAGS.joinToString(", ")).remove()
        document.allElements
            .filter { el -> isBlocked(el) }
            .forEach { it.remove() }
    }

    private fun isBlocked(element: Element): Boolean {
        val marker = (element.id() + " " + element.className()).lowercase()
        if (marker.isBlank()) return false
        return BLOCKLIST.any { marker.contains(it) }
    }

    /** Highest score among block containers; score rewards text and penalises link-heavy nodes. */
    private fun bestByDensity(document: Document): Element? =
        document.select("div, section, td, article, main")
            .filter { textLength(it) > 0 }
            .maxByOrNull { score(it) }

    /**
     * Density-adjusted text length: `text · (1 − linkDensity)` reduces to `text − linkText`, so a
     * nav block (text that is almost all link anchors) scores near zero while prose scores high.
     */
    private fun score(element: Element): Int = textLength(element) - linkTextLength(element)

    private fun textLength(element: Element): Int = element.text().length

    private fun linkTextLength(element: Element): Int =
        element.select("a").sumOf { it.text().length }
}
