# 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,
)

DEAD_CODE_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
    (re.compile(r"\bif\s*\(\s*false\s*\)"), "if (false) branch"),
    (re.compile(r"^\s*if\s+False\s*:"), "if False: branch"),
    (re.compile(r"^\s*if\s+0\s*:"), "if 0: branch"),
    (re.compile(r"\bwhile\s*\(\s*false\s*\)"), "while (false) loop"),
)

ABSTRACTION_SUFFIX_PATTERN = re.compile(
    r"class\s+\w*(Factory|Manager|Provider|Strategy|Handler|Wrapper|Helper|Service|Orchestrator|Coordinator|Impl)\b"
)
CLASS_PATTERN = re.compile(r"^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)", re.MULTILINE)

UNIFORM_INDENT_DEVIATION: float = 0.45
UNIFORM_MIN_SLOC: int = 50
GOD_MODULE_SLOC: int = 600
GOD_MODULE_IMPORTS: int = 20
DUPLICATION_THRESHOLD: float = 0.08
LONG_FUNCTION_LINES: int = 120
COMPLEXITY_THRESHOLD: int = 15
ABSTRACTION_DENSITY: float = 0.5
ABSTRACTION_MIN_CLASSES: int = 3


def detect_structure(context: FileContext) -> list[Signal]:
    metrics = context.metrics
    if metrics is None:
        return []
    findings: list[Signal] = []
    if metrics.sloc >= UNIFORM_MIN_SLOC and metrics.indent_variance < UNIFORM_INDENT_DEVIATION:
        findings.append(
            Signal(
                "MACHINE_REGULARITY",
                f"Unnaturally uniform whitespace (indent deviation {metrics.indent_variance:.2f})",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                2.0,
                1,
                f"sloc={metrics.sloc}",
            )
        )
    if (
        context.language == "python"
        and metrics.function_count >= 4
        and metrics.docstring_count >= metrics.function_count
    ):
        findings.append(
            Signal(
                "DOCSTRING_UNIFORMITY",
                f"Every one of {metrics.function_count} functions carries a docstring template",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                2.0,
                1,
                f"{metrics.docstring_count}/{metrics.function_count} docstrings",
            )
        )
    if metrics.sloc > GOD_MODULE_SLOC and metrics.import_count > GOD_MODULE_IMPORTS:
        findings.append(
            Signal(
                "GOD_MODULE",
                f"Module mixes many concerns ({metrics.sloc} SLOC, {metrics.import_count} imports)",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                3.0,
                1,
                f"sloc={metrics.sloc} imports={metrics.import_count}",
            )
        )
    if metrics.duplication_ratio > DUPLICATION_THRESHOLD:
        findings.append(
            Signal(
                "DUPLICATED_LOGIC",
                f"Duplicated block ratio {metrics.duplication_ratio:.1%}",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                3.0,
                1,
                f"windows duplicated: {metrics.duplication_ratio:.1%}",
            )
        )
    if metrics.max_function_length > LONG_FUNCTION_LINES:
        findings.append(
            Signal(
                "LONG_FUNCTION",
                f"Function of {metrics.max_function_length} lines",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                1.0,
                1,
                f"max function length {metrics.max_function_length}",
            )
        )
    if metrics.cyclomatic_max_estimate > COMPLEXITY_THRESHOLD:
        findings.append(
            Signal(
                "HIGH_COMPLEXITY",
                f"Estimated cyclomatic complexity {metrics.cyclomatic_max_estimate} per function",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                2.0,
                1,
                f"complexity estimate {metrics.cyclomatic_max_estimate}",
            )
        )
    for number, line in enumerate(context.lines, start=1):
        for pattern, title in DEAD_CODE_PATTERNS:
            if pattern.search(line):
                findings.append(
                    Signal("DEAD_DEFENSIVE_CODE", f"Dead code: {title}", SEVERITY_MEDIUM, AXIS_QUALITY, 2.0, number, line.strip())
                )
                break
    classes = CLASS_PATTERN.findall(context.text)
    if len(classes) >= ABSTRACTION_MIN_CLASSES:
        pattern_classes = ABSTRACTION_SUFFIX_PATTERN.findall(context.text)
        if len(pattern_classes) / len(classes) >= ABSTRACTION_DENSITY:
            findings.append(
                Signal(
                    "OVER_ABSTRACTION",
                    f"{len(pattern_classes)} of {len(classes)} classes are pattern-suffixed abstractions",
                    SEVERITY_MEDIUM,
                    AXIS_QUALITY,
                    3.0,
                    1,
                    ", ".join(pattern_classes[:5]),
                )
            )
    return findings
