# retoor <retoor@molodetz.nl>
from __future__ import annotations

import re

from devplacepy.services.jobs.isslop.analysis.signals import (
    AXIS_ORIGIN,
    AXIS_QUALITY,
    SEVERITY_MEDIUM,
    SEVERITY_STRONG,
    SEVERITY_WEAK,
    FileContext,
    Signal,
)

WEB_LANGUAGES: frozenset[str] = frozenset({"html", "css", "javascript", "typescript", "markdown"})

BUILDER_FINGERPRINTS: tuple[tuple[re.Pattern[str], str], ...] = (
    (re.compile(r"cdn\.gpteng\.co|gptengineer\.js", re.IGNORECASE), "GPT Engineer runtime script"),
    (re.compile(r"lovable-uploads|data-lov-id|lovable\.app", re.IGNORECASE), "Lovable builder artifact"),
    (re.compile(r"bolt\.new", re.IGNORECASE), "Bolt.new builder reference"),
    (re.compile(r"(made|built|created|generated)\s+with\s+(lovable|bolt|v0|base44|windsurf|cursor|replit agent)", re.IGNORECASE), "AI builder attribution"),
    (re.compile(r"<meta[^>]+generator[^>]+(lovable|bolt|v0|base44|framer|durable|10web|hostinger ai)", re.IGNORECASE), "AI site generator meta tag"),
    (re.compile(r"v0\.dev|vercel\.ai/v0", re.IGNORECASE), "Vercel v0 builder reference"),
)

SIGNATURE_GRADIENT_PATTERN = re.compile(r"#667eea.{0,80}#764ba2|#764ba2.{0,80}#667eea", re.IGNORECASE | re.DOTALL)
GENERIC_PURPLE_GRADIENT_PATTERN = re.compile(
    r"linear-gradient\([^)]*(#6366f1|#8b5cf6|#a855f7|#7c3aed|#4f46e5|#818cf8)[^)]*\)", re.IGNORECASE
)

SECTION_COMMENT_PATTERN = re.compile(
    r"<!--\s*([\w][\w /&'-]{0,40}\s+section|hero|navigation|navbar|footer|header)\s*-->",
    re.IGNORECASE,
)
EDIT_NARRATION_PATTERN = re.compile(
    r"<!--\s*(updated|added|new|fixed|changed|modified|improved|enhanced|revised)\b[^>]{0,60}-->",
    re.IGNORECASE,
)
VIBE_STACK_INDICATORS: tuple[tuple[re.Pattern[str], int, str], ...] = (
    (re.compile(r"lucide", re.IGNORECASE), 5, "lucide icons"),
    (re.compile(r"data-radix|radix-ui|\bradix\b", re.IGNORECASE), 5, "radix primitives"),
    (re.compile(r"shadcn", re.IGNORECASE), 1, "shadcn/ui"),
    (re.compile(r"__next|/_next/", re.IGNORECASE), 2, "next.js runtime"),
    (re.compile(r"\bInter\b"), 3, "inter font"),
    (re.compile(r"framer-motion|data-framer", re.IGNORECASE), 2, "framer motion"),
    (re.compile(r"class=\"[^\"]*flex[^\"]*items-center[^\"]*justify-center[^\"]*\""), 6, "tailwind utility soup"),
    (re.compile(r"animate-(fade|pulse|bounce|spin|in)", re.IGNORECASE), 8, "animation utilities"),
)
INLINE_MONOLITH_STYLE_PATTERN = re.compile(r"<style[^>]*>([\s\S]*?)</style>", re.IGNORECASE)
INLINE_MONOLITH_SCRIPT_PATTERN = re.compile(r"<script(?![^>]*src)[^>]*>([\s\S]*?)</script>", re.IGNORECASE)
MONOLITH_STYLE_MIN_LINES: int = 80
MONOLITH_SCRIPT_MIN_LINES: int = 40
LANDING_SECTION_PATTERN = re.compile(
    r"(?:class|id)\s*=\s*[\"'][^\"']*\b(hero|features|testimonials|pricing|faq|cta)\b", re.IGNORECASE
)

PLACEHOLDER_CONTACT_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
    (re.compile(r"\b(john|jane)(\.doe)?@example\.com\b", re.IGNORECASE), "Placeholder person email"),
    (re.compile(r"\b(info|contact|hello|support|your)@(example|yourcompany|yourdomain|company|yoursite)\.(com|org)\b", re.IGNORECASE), "Placeholder contact email"),
    (re.compile(r"\(555\)\s?\d{3}[- ]\d{4}|\+1\s?\(?555\)?[- ]\d{3,4}[- ]\d{4}|\b555-01\d{2}\b"), "Placeholder 555 phone number"),
    (re.compile(r"\b123 Main (Street|St)\b", re.IGNORECASE), "Placeholder street address"),
    (re.compile(r"\bYour Company( Name)?\b"), "Placeholder company name"),
)

LOREM_PATTERN = re.compile(r"lorem ipsum dolor", re.IGNORECASE)
DEAD_LINK_PATTERN = re.compile(r"href\s*=\s*[\"']#[\"']")
TAILWIND_CDN_PATTERN = re.compile(r"cdn\.tailwindcss\.com", re.IGNORECASE)
FONT_STACK_PATTERN = re.compile(r"fonts\.googleapis\.com[^\"']*(Inter|Poppins)", re.IGNORECASE)
ICON_CDN_PATTERN = re.compile(r"(font-?awesome|cdnjs[^\"']*all\.min\.css|lucide)", re.IGNORECASE)

SMOOTH_SCROLL_PATTERN = re.compile(r"scroll-behavior\s*:\s*smooth|scrollIntoView\s*\(\s*\{\s*behavior\s*:\s*[\"']smooth")
OBSERVER_REVEAL_PATTERN = re.compile(
    r"IntersectionObserver[\s\S]{0,400}classList\.(add|toggle)\s*\(\s*[\"'](visible|show|fade-in|animate|active|in-view)[\"']"
)
ANCHOR_SCRIPT_PATTERN = re.compile(r"querySelectorAll\s*\(\s*[\"']a\[href\^=[\"'\\]+#")

ROOT_PALETTE_PATTERN = re.compile(r"--primary(-color)?\s*:[\s\S]{0,300}--secondary(-color)?\s*:", re.IGNORECASE)
UNIVERSAL_RESET_PATTERN = re.compile(
    r"\*\s*\{[^}]*margin\s*:\s*0[^}]*padding\s*:\s*0[^}]*box-sizing\s*:\s*border-box[^}]*\}", re.DOTALL
)

HEADING_EMOJI_PATTERN = re.compile(r"<(h[1-3]|button)[^>]*>[^<]*[\U0001F300-\U0001FAFF✨🚀⭐✅]")
STARTUP_COPY_PATTERN = re.compile(
    r"empower your (team|business)|built for (modern|the) (teams|businesses|ai era|ai age|modern web)"
    r"|unlock (the power|your potential|productivity)|transform your (business|workflow|ideas)"
    r"|elevate your|seamless integration|all rights reserved\.\s*(built|made) with",
    re.IGNORECASE,
)

SECTION_COMMENT_THRESHOLD: int = 3
LANDING_SECTION_THRESHOLD: int = 4
DEAD_LINK_THRESHOLD: int = 5


def _line_of(text: str, position: int) -> int:
    return text.count("\n", 0, position) + 1


def detect_web_tells(context: FileContext) -> list[Signal]:
    if context.language not in WEB_LANGUAGES:
        return []
    findings: list[Signal] = []
    text = context.text

    for pattern, title in BUILDER_FINGERPRINTS:
        match = pattern.search(text)
        if match:
            findings.append(
                Signal("AI_BUILDER_FINGERPRINT", title, SEVERITY_STRONG, AXIS_ORIGIN, 4.0, _line_of(text, match.start()), match.group(0)[:120])
            )

    signature = SIGNATURE_GRADIENT_PATTERN.search(text)
    if signature:
        findings.append(
            Signal(
                "SIGNATURE_GRADIENT",
                "Canonical LLM purple gradient #667eea to #764ba2",
                SEVERITY_STRONG,
                AXIS_ORIGIN,
                3.0,
                _line_of(text, signature.start()),
                signature.group(0)[:120],
            )
        )
    else:
        generic = GENERIC_PURPLE_GRADIENT_PATTERN.search(text)
        if generic:
            findings.append(
                Signal(
                    "PURPLE_GRADIENT",
                    "Indigo or violet gradient from default LLM palette",
                    SEVERITY_MEDIUM,
                    AXIS_ORIGIN,
                    1.0,
                    _line_of(text, generic.start()),
                    generic.group(0)[:120],
                )
            )

    stack_hits: list[str] = []
    for pattern, minimum, label in VIBE_STACK_INDICATORS:
        if len(pattern.findall(text)) >= minimum:
            stack_hits.append(label)
    if len(stack_hits) >= 3:
        findings.append(
            Signal(
                "VIBE_STACK",
                f"Default AI frontend stack fingerprint ({len(stack_hits)} indicators)",
                SEVERITY_STRONG if len(stack_hits) >= 5 else SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                3.0,
                1,
                ", ".join(stack_hits),
            )
        )

    if context.language == "html":
        section_comments = SECTION_COMMENT_PATTERN.findall(text)
        if len(section_comments) >= SECTION_COMMENT_THRESHOLD:
            findings.append(
                Signal(
                    "SECTION_BANNER_COMMENTS",
                    f"{len(section_comments)} template section banner comments",
                    SEVERITY_MEDIUM,
                    AXIS_ORIGIN,
                    2.0,
                    1,
                    ", ".join(section_comments[:6]),
                )
            )
        edit_narrations = EDIT_NARRATION_PATTERN.findall(text)
        if edit_narrations:
            match = EDIT_NARRATION_PATTERN.search(text)
            findings.append(
                Signal(
                    "EDIT_NARRATION_COMMENT",
                    f"{len(edit_narrations)} chat-edit narration comments (Updated/Added/New ...)",
                    SEVERITY_STRONG,
                    AXIS_ORIGIN,
                    3.0,
                    _line_of(text, match.start()) if match else 1,
                    match.group(0)[:100] if match else "",
                )
            )
        style_blocks = INLINE_MONOLITH_STYLE_PATTERN.findall(text)
        script_blocks = INLINE_MONOLITH_SCRIPT_PATTERN.findall(text)
        style_lines = sum(block.count("\n") for block in style_blocks)
        script_lines = sum(block.count("\n") for block in script_blocks)
        if style_lines >= MONOLITH_STYLE_MIN_LINES and script_lines >= MONOLITH_SCRIPT_MIN_LINES:
            findings.append(
                Signal(
                    "SINGLE_FILE_MONOLITH",
                    f"Single-file page with {style_lines} CSS and {script_lines} JS lines inlined, the chat-output shape",
                    SEVERITY_MEDIUM,
                    AXIS_ORIGIN,
                    2.0,
                    1,
                    f"inline style {style_lines} lines, inline script {script_lines} lines",
                )
            )
        landing_sections = {match.group(1).lower() for match in LANDING_SECTION_PATTERN.finditer(text)}
        if len(landing_sections) >= LANDING_SECTION_THRESHOLD:
            findings.append(
                Signal(
                    "LANDING_TEMPLATE",
                    "Canonical hero-features-testimonials-pricing landing structure",
                    SEVERITY_MEDIUM,
                    AXIS_ORIGIN,
                    2.0,
                    1,
                    ", ".join(sorted(landing_sections)),
                )
            )
        dead_links = DEAD_LINK_PATTERN.findall(text)
        if len(dead_links) >= DEAD_LINK_THRESHOLD:
            findings.append(
                Signal(
                    "DEAD_ANCHOR_LINKS",
                    f'{len(dead_links)} links pointing to href="#"',
                    SEVERITY_MEDIUM,
                    AXIS_QUALITY,
                    1.0,
                    1,
                    f"{len(dead_links)} placeholder links",
                )
            )
        if TAILWIND_CDN_PATTERN.search(text):
            findings.append(
                Signal("TAILWIND_CDN", "Tailwind loaded from CDN, the LLM prototype default", SEVERITY_WEAK, AXIS_ORIGIN, 1.0, 1, "cdn.tailwindcss.com")
            )
        fonts = FONT_STACK_PATTERN.search(text)
        if fonts and ICON_CDN_PATTERN.search(text):
            findings.append(
                Signal("DEFAULT_FONT_STACK", f"Default LLM font and icon stack ({fonts.group(1)} plus icon CDN)", SEVERITY_WEAK, AXIS_ORIGIN, 1.0, _line_of(text, fonts.start()), fonts.group(0)[:100])
            )
        emoji_heading = HEADING_EMOJI_PATTERN.search(text)
        if emoji_heading:
            findings.append(
                Signal("EMOJI_HEADINGS", "Emoji inside headings or buttons", SEVERITY_MEDIUM, AXIS_ORIGIN, 1.0, _line_of(text, emoji_heading.start()), emoji_heading.group(0)[:100])
            )

    for pattern, title in PLACEHOLDER_CONTACT_PATTERNS:
        match = pattern.search(text)
        if match:
            findings.append(
                Signal("PLACEHOLDER_CONTENT", title, SEVERITY_MEDIUM, AXIS_QUALITY, 2.0, _line_of(text, match.start()), match.group(0)[:100])
            )

    lorem = LOREM_PATTERN.search(text)
    if lorem:
        findings.append(
            Signal("PLACEHOLDER_CONTENT", "Lorem ipsum filler text left in source", SEVERITY_MEDIUM, AXIS_QUALITY, 2.0, _line_of(text, lorem.start()), lorem.group(0))
        )

    copy_match = STARTUP_COPY_PATTERN.search(text)
    if copy_match:
        findings.append(
            Signal("TEMPLATE_COPY", "Stock AI landing page copy phrase", SEVERITY_MEDIUM, AXIS_ORIGIN, 1.0, _line_of(text, copy_match.start()), copy_match.group(0)[:100])
        )

    if context.language in ("javascript", "typescript", "html"):
        observer = OBSERVER_REVEAL_PATTERN.search(text)
        if observer:
            findings.append(
                Signal(
                    "SCROLL_REVEAL_BOILERPLATE",
                    "IntersectionObserver fade-in reveal boilerplate",
                    SEVERITY_MEDIUM,
                    AXIS_ORIGIN,
                    1.0,
                    _line_of(text, observer.start()),
                    observer.group(0)[:100],
                )
            )
        if SMOOTH_SCROLL_PATTERN.search(text) and ANCHOR_SCRIPT_PATTERN.search(text):
            findings.append(
                Signal("SMOOTH_SCROLL_BOILERPLATE", "Smooth-scroll anchor polyfill boilerplate", SEVERITY_MEDIUM, AXIS_ORIGIN, 1.0, 1, "smooth scroll + anchor querySelectorAll")
            )

    if context.language in ("css", "html"):
        palette = ROOT_PALETTE_PATTERN.search(text)
        if palette:
            findings.append(
                Signal("DEFAULT_CSS_PALETTE", "Generic --primary/--secondary variable palette", SEVERITY_MEDIUM, AXIS_ORIGIN, 1.0, _line_of(text, palette.start()), palette.group(0)[:80])
            )
        reset = UNIVERSAL_RESET_PATTERN.search(text)
        if reset:
            findings.append(
                Signal("UNIVERSAL_RESET", "Universal margin/padding/box-sizing reset block", SEVERITY_WEAK, AXIS_ORIGIN, 1.0, _line_of(text, reset.start()), "* { margin:0; padding:0; box-sizing:border-box }")
            )

    return findings[:14]
