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

import re

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

DECLARATION_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = {
    "python": (
        re.compile(r"^\s*(?:async\s+)?def\s+(\w+)"),
        re.compile(r"^\s*class\s+(\w+)"),
        re.compile(r"^\s*([a-zA-Z_]\w*)\s*(?::[^=]+)?=\s*[^=]"),
    ),
    "javascript": (
        re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)"),
        re.compile(r"^\s*(?:export\s+)?class\s+(\w+)"),
        re.compile(r"^\s*(?:const|let|var)\s+(\w+)"),
    ),
}
DECLARATION_PATTERNS["typescript"] = DECLARATION_PATTERNS["javascript"]

GENERIC_IDENTIFIERS: frozenset[str] = frozenset(
    {
        "data", "result", "res", "temp", "tmp", "item", "items", "value", "values", "val",
        "obj", "object", "thing", "stuff", "info", "foo", "bar", "baz", "output", "input",
        "myfunction", "myvar", "mydata", "example", "processdata", "dosomething", "handledata",
        "handleclick", "handlesubmit", "handlechange", "mycomponent", "untitled", "test1", "func",
        "arr", "lst", "dct", "retval", "resp", "ret",
    }
)

VERBOSE_MIN_LENGTH: int = 30
VERBOSE_MIN_WORDS: int = 5
GENERIC_DENSITY_THRESHOLD: float = 0.30
MIN_DECLARATIONS: int = 5


def _split_words(identifier: str) -> list[str]:
    parts = re.split(r"_+", identifier)
    words: list[str] = []
    for part in parts:
        words.extend(re.findall(r"[A-Z]?[a-z0-9]+|[A-Z]+(?![a-z])", part))
    return [word for word in words if word]


def detect_naming(context: FileContext) -> list[Signal]:
    patterns = DECLARATION_PATTERNS.get(context.language)
    if not patterns:
        return []
    declarations: list[tuple[int, str]] = []
    for number, line in enumerate(context.lines, start=1):
        for pattern in patterns:
            match = pattern.match(line)
            if match:
                name = next(group for group in match.groups() if group)
                declarations.append((number, name))
                break
    if len(declarations) < MIN_DECLARATIONS:
        return []
    findings: list[Signal] = []
    generic = [(number, name) for number, name in declarations if name.lower().replace("_", "") in GENERIC_IDENTIFIERS]
    density = len(generic) / len(declarations)
    if density >= GENERIC_DENSITY_THRESHOLD:
        number, name = generic[0]
        findings.append(
            Signal(
                "GENERIC_NAMING",
                f"Generic identifiers in {density:.0%} of {len(declarations)} declarations",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                2.0,
                number,
                name,
            )
        )
    verbose = [
        (number, name)
        for number, name in declarations
        if len(name) >= VERBOSE_MIN_LENGTH and len(_split_words(name)) >= VERBOSE_MIN_WORDS
    ]
    if verbose:
        number, name = verbose[0]
        findings.append(
            Signal(
                "TEXTBOOK_NAMING",
                "Over-verbose textbook identifier",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                2.0,
                number,
                name,
            )
        )
    snake = sum(1 for _, name in declarations if "_" in name and name.lower() == name)
    camel = sum(1 for _, name in declarations if "_" not in name and name.lower() != name and name[0].islower())
    total = len(declarations)
    if snake >= 3 and camel >= 3 and snake / total >= 0.25 and camel / total >= 0.25:
        findings.append(
            Signal(
                "NAMING_MIXED",
                f"Mixed snake_case ({snake}) and camelCase ({camel}) declarations",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                1.0,
                declarations[0][0],
                f"{snake} snake_case vs {camel} camelCase",
            )
        )
    return findings
