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

import logging
from pathlib import Path
from typing import Optional

from devplacepy.services.jobs.isslop.config import ANALYSIS_FILE_CAP_BYTES

logger = logging.getLogger(__name__)

EXCLUDED_DIRECTORIES: frozenset[str] = frozenset(
    {
        ".git",
        ".hg",
        ".svn",
        "node_modules",
        "bower_components",
        "vendor",
        "third_party",
        "thirdparty",
        "__pycache__",
        ".venv",
        "venv",
        "env",
        ".env",
        "dist",
        "build",
        "out",
        "target",
        "coverage",
        ".next",
        ".nuxt",
        ".cache",
        "site-packages",
        ".idea",
        ".vscode",
        ".tox",
        ".mypy_cache",
        ".pytest_cache",
        ".terraform",
        "migrations",
    }
)

LOCKFILE_NAMES: frozenset[str] = frozenset(
    {
        "package-lock.json",
        "yarn.lock",
        "pnpm-lock.yaml",
        "poetry.lock",
        "pipfile.lock",
        "gemfile.lock",
        "go.sum",
        "cargo.lock",
        "composer.lock",
        "uv.lock",
        "bun.lockb",
    }
)

GENERATED_SUFFIXES: tuple[str, ...] = (
    ".min.js",
    ".min.css",
    ".map",
    ".pb.go",
    "_pb2.py",
    "_pb2_grpc.py",
    ".bundle.js",
    ".chunk.js",
    ".d.ts",
)

BINARY_EXTENSIONS: frozenset[str] = frozenset(
    {
        ".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".bmp", ".ico", ".tiff", ".svgz",
        ".woff", ".woff2", ".ttf", ".otf", ".eot",
        ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar",
        ".mp3", ".mp4", ".wav", ".ogg", ".webm", ".avi", ".mov", ".flac",
        ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
        ".exe", ".dll", ".so", ".dylib", ".bin", ".dat", ".db", ".sqlite", ".sqlite3",
        ".pyc", ".pyo", ".class", ".jar", ".war", ".wasm", ".o", ".a",
    }
)

GENERATED_MARKERS: tuple[str, ...] = (
    "do not edit",
    "auto-generated",
    "autogenerated",
    "@generated",
    "code generated by",
    "this file was generated",
)

MINIFIED_AVG_LINE_LENGTH: int = 300


def is_excluded_directory(part: str) -> bool:
    return part.lower() in EXCLUDED_DIRECTORIES


def exclusion_reason(path: Path, workspace: Path) -> Optional[str]:
    relative = path.relative_to(workspace)
    for part in relative.parts[:-1]:
        if is_excluded_directory(part):
            return f"vendored or generated directory: {part}"
    name = path.name.lower()
    if name in LOCKFILE_NAMES:
        return "lockfile"
    for suffix in GENERATED_SUFFIXES:
        if name.endswith(suffix):
            return f"generated artifact ({suffix})"
    if path.suffix.lower() in BINARY_EXTENSIONS:
        return "binary file"
    try:
        size = path.stat().st_size
    except OSError as error:
        return f"unreadable: {error}"
    if size == 0:
        return "empty file"
    if size > ANALYSIS_FILE_CAP_BYTES:
        return f"oversize ({size} bytes)"
    try:
        with path.open("rb") as handle:
            head = handle.read(8192)
    except OSError as error:
        return f"unreadable: {error}"
    if b"\x00" in head:
        return "binary content"
    text_head = head.decode("utf-8", errors="replace").lower()
    for marker in GENERATED_MARKERS:
        if marker in text_head[:600]:
            return f"generated marker: {marker}"
    return None


def looks_minified(text: str) -> bool:
    lines = [line for line in text.splitlines() if line.strip()]
    if not lines:
        return False
    average = sum(len(line) for line in lines) / len(lines)
    return average > MINIFIED_AVG_LINE_LENGTH
