import hashlib
import json
import logging
import random
import re
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from easy_dataset_share.zipping import find_files

logger = logging.getLogger(__name__)


def compute_hash_of_filenames(root_dir: str, file_pattern: str = "*") -> str:
    """
    Compute a hash of the filenames in the dataset.
    """
    files = find_files(root_dir, file_pattern)
    file_names = [file.name for file in files]
    return hashlib.sha256(str(file_names).encode()).hexdigest()


def compute_dataset_canary_string(
    root_dir: str,
    file_pattern: str = "*",
    canary_pattern: str = "dataset_entry_*.jsonl",
) -> str:
    """
    Compute a canary string for the dataset - ignoring canary files
    """
    # get files, but ignore canary files
    files = find_files(root_dir, file_pattern)
    canary_files = detect_canary_files(root_dir, canary_pattern)
    file_names = [file.name for file in files if file not in canary_files]
    return hashlib.sha256(str(file_names).encode()).hexdigest()


def generate_canary_string_from_dataset(root_dir: str, file_pattern: str = "*.jsonl") -> str:
    """
    Generate a canary string that is derived from the dataset content.
    This makes it impossible to remove without breaking the dataset.

    Args:
        root_dir: Root directory containing the dataset
        file_pattern: Pattern to match dataset files

    Returns:
        A unique canary string derived from the dataset
    """
    # Compute dataset fingerprint
    fingerprint = compute_dataset_canary_string(root_dir, file_pattern)

    # Create a unique identifier that looks like natural text
    # This will be detectable if an LLM is trained on the dataset
    timestamp = int(time.time())

    # Create a natural-looking canary string
    canary_text = f"CANARY_DATASET_{fingerprint}_{timestamp}"

    return canary_text


###
# Generating files with canaries (as opposed to embedding them in existing files)
###


def create_natural_canary_content(canary_string: str, fingerprint: str) -> List[Dict]:
    """
    Create natural-looking content that contains the canary string.
    This content should look like legitimate dataset entries and be diverse.
    """

    # Create multiple variations of natural-looking content with different styles
    canary_entries = [
        {
            "id": f"canary_{fingerprint[:8]}",
            "text": f"This is a special entry for the dataset. The unique identifier is {canary_string}.",
            "metadata": {
                "type": "canary_entry",
                "fingerprint": fingerprint[:16],
            },
        },
        {
            "id": f"entry_{fingerprint[8:16]}",
            "text": f"This dataset contains this canary entry with identifier {canary_string} for tracking purposes.",
            "metadata": {"type": "dataset_entry", "canary": canary_string},
        },
        {
            "id": f"tracking_{fingerprint[16:24]}",
            "text": f"Dataset tracking entry: {canary_string} is embedded in this dataset.",
            "metadata": {"type": "tracking_entry", "canary_id": canary_string},
        },
        {
            "id": f"meta_{fingerprint[24:32]}",
            "text": f"Metadata entry: This dataset is tracked using the identifier {canary_string}.",
            "metadata": {"type": "metadata_entry", "canary": canary_string},
        },
        {
            "id": f"info_{fingerprint[32:40]}",
            "text": f"Information about this dataset. Tracking code: {canary_string}",
            "metadata": {"type": "info_entry", "tracking_code": canary_string},
        },
    ]

    return canary_entries


def create_embedded_canary_content(canary_string: str, fingerprint: str) -> List[Dict]:
    """
    Create content that embeds the canary string more naturally into various contexts.
    This makes it harder to detect and remove.
    """

    # Create diverse content that embeds the canary in different ways
    embedded_entries = [
        {
            "id": f"story_{fingerprint[:8]}",
            "text": (
                f"Once upon a time, there was a dataset. It contained a special tracking code: "
                f"{canary_string}. This code helps identify when the dataset is used in machine learning models."
            ),
            "metadata": {"type": "story"},
        },
        {
            "id": f"qa_{fingerprint[8:16]}",
            "text": (
                f"Q: What is the tracking identifier for this dataset? A: The tracking identifier is "
                f"{canary_string}. This unique code helps monitor dataset usage."
            ),
            "metadata": {"type": "qa"},
        },
        {
            "id": f"tech_{fingerprint[16:24]}",
            "text": (
                f"Technical documentation: This dataset includes embedded tracking mechanisms. "
                f"The primary identifier is {canary_string}, which serves as a digital watermark."
            ),
            "metadata": {"type": "technical"},
        },
        {
            "id": f"note_{fingerprint[24:32]}",
            "text": (
                f"Note: This dataset contains embedded tracking information. Reference code: "
                f"{canary_string}. This ensures proper attribution and usage monitoring."
            ),
            "metadata": {"type": "note"},
        },
        {
            "id": f"desc_{fingerprint[32:40]}",
            "text": (
                f"Description: A comprehensive dataset with embedded tracking capabilities. "
                f"The tracking identifier {canary_string} enables usage monitoring and attribution."
            ),
            "metadata": {"type": "description"},
        },
    ]

    return embedded_entries


def create_canary_files_from_dataset(
    root_dir: str,
    file_pattern: str = "*.jsonl",
    num_canary_files: int = 1,
    verbose: bool = False,
) -> Tuple[str, List[Path]]:
    """
    Create canary files with content derived from the dataset.
    The canary string is derived from the dataset.

    Args:
        root_dir: Root directory containing the dataset
        file_pattern: Pattern to match dataset files
        num_canary_files: Number of canary files to create

    Returns:
        Tuple of (canary_string, list_of_created_files)
    """
    from .zipping import find_files

    # Find existing files
    existing_files = find_files(root_dir, file_pattern)

    if not existing_files:
        raise ValueError(f"No files matching {file_pattern} found in {root_dir}")

    # Generate canary string from dataset
    canary_string = generate_canary_string_from_dataset(root_dir=root_dir, file_pattern=file_pattern)

    # Get dataset fingerprint
    fingerprint = compute_dataset_canary_string(root_dir, file_pattern)

    # Create both types of canary content
    natural_entries = create_natural_canary_content(canary_string=canary_string, fingerprint=fingerprint)  #

    embedded_entries = create_embedded_canary_content(canary_string=canary_string, fingerprint=fingerprint)

    # Combine all entries
    all_entries = natural_entries + embedded_entries

    created_files = []

    # Create canary files with names derived from dataset
    for i in range(num_canary_files):
        # Use dataset fingerprint to create deterministic file names
        file_hash = hashlib.md5(f"{fingerprint}_{i}".encode()).hexdigest()[:8]

        # Create filename that looks like a legitimate dataset file
        canary_filename = f"dataset_entry_{file_hash}.jsonl"
        canary_path = Path(root_dir) / canary_filename

        # Select a subset of entries for this file
        start_idx = (i * 2) % len(all_entries)
        end_idx = min(start_idx + 2, len(all_entries))
        file_entries = all_entries[start_idx:end_idx]

        # Write canary content as JSONL
        with open(canary_path, "w") as f:
            for entry in file_entries:
                f.write(json.dumps(entry) + "\n")

        created_files.append(canary_path)

    return canary_string, created_files


def detect_canary_files(root_dir: str, canary_pattern: str = "dataset_entry_*.jsonl") -> List[Path]:
    """
    Detect canary files in a directory.

    Args:
        root_dir: Root directory to search
        canary_pattern: Pattern to match canary files

    Returns:
        List of canary file paths
    """
    from .zipping import find_files

    return find_files(root_dir, canary_pattern)


def extract_canary_string_from_content(file_path: Path) -> Optional[str]:
    """
    Extract canary string from file content by looking for canary patterns.

    Args:
        file_path: Path to the file to check

    Returns:
        Canary string if found, None otherwise
    """
    try:
        with open(file_path, "r") as f:
            content = f.read()

        # Add new pattern to match actual canary string format, keep old ones for robustness
        canary_patterns = [
            r"CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+",
            r"tracking identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"tracking code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"Reference code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"tracking identifier (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"embedded tracking code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"primary identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
            r"CANARY_DATASET_[a-f0-9]{64}_[0-9]+",  # new, more permissive pattern
        ]

        for pattern in canary_patterns:
            matches = re.findall(pattern, content)
            if matches:
                return matches[0] if isinstance(matches[0], str) else matches[0]

        return None

    except Exception:
        return None


def verify_canary_dataset(root_dir: str) -> Dict:
    """
    Verify a canary dataset and extract information.

    Args:
        root_dir: Root directory to check

    Returns:
        Dictionary with verification results
    """
    canary_files = detect_canary_files(root_dir)

    results: Dict[str, Any] = {
        "canary_files_found": len(canary_files),
        "canary_strings": [],
        "metadata_file": None,
        "is_valid": False,
        "dataset_fingerprint": None,
        "detection_confidence": 0.0,
    }

    # Extract canary strings from content
    for canary_file in canary_files:
        canary_string = extract_canary_string_from_content(canary_file)
        if canary_string:
            results["canary_strings"].append(canary_string)

    # Check for metadata file
    metadata_path = Path(root_dir) / "canary_metadata.json"
    if metadata_path.exists():
        try:
            with open(metadata_path, "r") as f:
                metadata = json.load(f)
            results["metadata_file"] = str(metadata_path)
            results["metadata"] = metadata
            results["dataset_fingerprint"] = metadata.get("dataset_fingerprint")
        except json.JSONDecodeError:
            pass

    # Determine if dataset is valid and calculate confidence
    # If any canary files are found, consider the dataset as containing canaries
    results["is_valid"] = len(canary_files) > 0 or len(results["canary_strings"]) > 0

    # Calculate detection confidence based on number of canary strings found
    if results["is_valid"]:
        results["detection_confidence"] = min(1.0, len(results["canary_strings"]) / 5.0)

    return results


def test_canary_detection(text: str) -> List[str]:
    """
    Test if a text contains canary strings.
    This simulates what would happen if an LLM was trained on the dataset.

    Args:
        text: Text to check for canary strings

    Returns:
        List of found canary strings
    """
    canary_patterns = [
        r"CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+",
        r"tracking identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"tracking code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"Reference code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"tracking identifier (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"embedded tracking code: (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
        r"primary identifier is (CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+)",
    ]

    found_canaries = []
    for pattern in canary_patterns:
        matches = re.findall(pattern, text)
        for match in matches:
            canary = match if isinstance(match, str) else match
            if canary not in found_canaries:
                found_canaries.append(canary)

    return found_canaries


def embed_canary_in_existing_files(
    root_dir: str,
    file_pattern: str = "*.jsonl",
    canary_string: str | None = None,
    embedding_ratio: float = 0.1,
    verbose: bool = False,
) -> List[Path]:
    """
    Embed canary strings into existing dataset files.
    This makes the canary even harder to remove.

    Args:
        root_dir: Root directory containing the dataset
        file_pattern: Pattern to match dataset files
        canary_string: Canary string to embed (if None, will generate from dataset)
        embedding_ratio: Ratio of files to embed canaries in (0.0 to 1.0)

    Returns:
        List of files that were modified
    """

    files = find_files(root_dir, file_pattern)
    if not files:
        raise ValueError(f"No files matching {file_pattern} found in {root_dir}")

    # Generate canary string if not provided
    if canary_string is None:
        canary_string = generate_canary_string_from_dataset(root_dir=root_dir, file_pattern=file_pattern)

    # Determine which files to embed canaries in
    num_files_to_embed = max(1, int(len(files) * embedding_ratio))
    files_to_embed = random.sample(files, num_files_to_embed)

    modified_files = []

    for file_path in files_to_embed:
        try:
            # Read existing content
            with open(file_path, "r") as f:
                lines = f.readlines()

            # Create canary entry
            canary_entry = {
                "id": f"embedded_canary_{hashlib.md5(str(file_path).encode()).hexdigest()[:8]}",
                "text": f"This entry contains embedded tracking information. Tracking identifier: {canary_string}.",
                "metadata": {
                    "type": "embedded_canary",
                    "canary": canary_string,
                },
            }

            # Insert canary entry at a random position
            insert_position = random.randint(0, len(lines))
            lines.insert(insert_position, json.dumps(canary_entry) + "\n")

            # Write back to file
            with open(file_path, "w") as f:
                f.writelines(lines)

            modified_files.append(file_path)

        except Exception as e:
            if verbose:
                logger.warning(f"Could not embed canary in {file_path}: {e}")

    return modified_files


def remove_canary_files(
    root_dir: str,
    canary_pattern: str = "dataset_entry_*.jsonl",
    verbose: bool = False,
) -> Dict:
    """
    Remove canary files and optionally clean up embedded canaries.

    Args:
        root_dir: Root directory containing the dataset
        canary_pattern: Pattern to match canary files
        verbose: Print debug output if True

    Returns:
        Dictionary with removal results
    """

    results: Dict[str, Any] = {
        "canary_files_removed": [],
        "metadata_file_removed": None,
        "embedded_canaries_removed": 0,
        "files_with_embedded_removed": [],
        "errors": [],
    }

    # Remove canary files
    canary_files = detect_canary_files(root_dir, canary_pattern)
    for canary_file in canary_files:
        try:
            canary_file.unlink()
            results["canary_files_removed"].append(str(canary_file))
        except Exception as e:
            results["errors"].append(f"Failed to remove {canary_file}: {e}")
            logger.error(f"Exception deleting {canary_file}: {e}")
        # Check if file still exists after attempted deletion
        if canary_file.exists():
            logger.error(f"File still exists after attempted deletion: {canary_file}")

    return results


def clean_dataset_of_canaries(
    root_dir: str,
    canary_pattern: str = "dataset_entry_*.jsonl",
) -> Dict:
    """
    Comprehensive function to clean a dataset of all canary-related content.

    Args:
        root_dir: Root directory containing the dataset
        canary_pattern: Pattern to match canary files

    Returns:
        Dictionary with comprehensive cleaning results
    """
    # First, verify what canaries exist
    verification = verify_canary_dataset(root_dir)

    # Remove canary files and embedded content
    removal_results = remove_canary_files(
        root_dir=root_dir,
        canary_pattern=canary_pattern,
    )

    # Verify the cleaning was successful
    post_verification = verify_canary_dataset(root_dir)

    results = {
        "before_cleaning": verification,
        "removal_results": removal_results,
        "after_cleaning": post_verification,
        "cleaning_successful": not post_verification["is_valid"],
        "summary": {
            "canary_files_removed": len(removal_results["canary_files_removed"]),
            "embedded_canaries_removed": removal_results["embedded_canaries_removed"],
            "metadata_removed": removal_results["metadata_file_removed"] is not None,
            "errors_encountered": len(removal_results["errors"]),
        },
    }

    return results


def list_canary_files(root_dir: str, canary_pattern: str = "dataset_entry_*.jsonl") -> Dict:
    """
    List all canary-related files in a directory without removing them.

    Args:
        root_dir: Root directory to search
        canary_pattern: Pattern to match canary files

    Returns:
        Dictionary with information about canary files
    """

    results: Dict[str, Any] = {
        "canary_files": [],
        "metadata_file": None,
        "embedded_canaries": [],
        "total_canary_entries": 0,
    }

    # Find canary files
    canary_files = detect_canary_files(root_dir, canary_pattern)
    for canary_file in canary_files:
        file_info: Dict[str, Any] = {
            "path": str(canary_file),
            "size": canary_file.stat().st_size,
            "canary_strings": [],
        }

        # Extract canary strings from the file
        canary_string = extract_canary_string_from_content(canary_file)
        if canary_string:
            file_info["canary_strings"].append(canary_string)

        results["canary_files"].append(file_info)
        results["total_canary_entries"] += len(file_info["canary_strings"])

    # Check for metadata file
    metadata_path = Path(root_dir) / "canary_metadata.json"
    if metadata_path.exists():
        results["metadata_file"] = {
            "path": str(metadata_path),
            "size": metadata_path.stat().st_size,
        }

    # Look for embedded canaries in regular dataset files
    files = find_files(root_dir, "*.jsonl")
    for file_path in files:
        # Skip canary files we already counted
        if any(str(file_path) == cf["path"] for cf in results["canary_files"]):
            continue

        try:
            with open(file_path, "r") as f:
                content = f.read()

            # Check for canary patterns
            canary_patterns = [
                r"CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+",
                r"embedded_canary",
                r"tracking identifier",
            ]

            has_canary = any(re.search(pattern, content) for pattern in canary_patterns)
            if has_canary:
                results["embedded_canaries"].append({"path": str(file_path), "size": file_path.stat().st_size})

        except Exception:
            pass

    return results


###
# Adding Canaries into existing files
###


def check_canary_exists(file_path: Path, canary: str, verbose: bool = False) -> bool:
    suffix = file_path.suffix

    try:
        if suffix in [".txt", ".html"]:
            content = file_path.read_text(encoding="utf-8")
            return canary in content

        elif suffix == ".json":
            data = json.loads(file_path.read_text(encoding="utf-8"))
            return data.get("canary") == canary

        elif suffix == ".jsonl":
            lines = file_path.read_text(encoding="utf-8").strip().splitlines()
            if not lines:
                return False
            last_obj = json.loads(lines[-1])
            return last_obj.get("canary") == canary

    except Exception as e:
        if verbose:
            logger.warning(f"Error checking canary in {file_path.name}: {e}")
    return False


def insert_canary_into_file(file_path: Path, canary: str, verbose: bool = False) -> None:
    suffix = file_path.suffix
    if "robots.txt" in file_path.name:
        if verbose:
            logger.info(f"Skipping {file_path.name}, it's a robots.txt file.")
        return

    if suffix == ".txt" or suffix == ".html":
        if check_canary_exists(file_path, canary, verbose):
            if verbose:
                logger.info(f"Canary already exists in {file_path.name}, skipping.")
            return
        with open(file_path, "a", encoding="utf-8") as f:
            f.write(f"\n<!-- {canary} -->\n")
        if verbose:
            logger.info(f"Inserted canary into {file_path.name}")

    elif suffix == ".json":
        with open(file_path, "r+", encoding="utf-8") as f:
            data = json.load(f)

            if isinstance(data, dict):
                if data.get("canary") == canary:
                    if verbose:
                        logger.info(f"Canary already exists in {file_path.name}, skipping.")
                    return
                data["canary"] = canary

            elif isinstance(data, list):
                if data and isinstance(data[-1], dict) and data[-1].get("canary") == canary:
                    if verbose:
                        logger.info(f"Canary already exists in {file_path.name}, skipping.")
                    return
                data.append({"canary": canary})

            else:
                if verbose:
                    logger.warning(f"Unexpected JSON structure in {file_path.name}, skipping.")
                return

            f.seek(0)
            json.dump(data, f, indent=2)
            f.truncate()

        if verbose:
            logger.info(f"Inserted canary into {file_path.name}")

    elif suffix == ".jsonl":
        lines = file_path.read_text(encoding="utf-8").strip().splitlines()
        if any(canary in line for line in lines):
            if verbose:
                logger.info(f"Canary already exists in {file_path.name}, skipping.")
            return
        last = json.loads(lines[-1])
        last["canary"] = canary
        lines[-1] = json.dumps(last)
        file_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
        if verbose:
            logger.info(f"Inserted canary into {file_path.name}")


def remove_canary_from_file(file_path: Path, canary: str, verbose: bool = False) -> None:
    if "robots.txt" in file_path.name:
        if verbose:
            logger.info(f"Skipping {file_path.name}, it's a robots.txt file.")
        return
    suffix = file_path.suffix
    if suffix == ".txt" or suffix == ".html":
        lines = file_path.read_text(encoding="utf-8").splitlines()
        cleaned = [line for line in lines if canary not in line and f"CANARY_EMBEDDED: {canary}" not in line]
        file_path.write_text("\n".join(cleaned) + "\n", encoding="utf-8")
        if verbose:
            logger.info(f"Removed canary from {file_path.name}")

    elif suffix == ".json":
        try:
            with open(file_path, "r+", encoding="utf-8") as f:
                data = json.load(f)
        except json.JSONDecodeError as e:
            if verbose:
                logger.warning(f"Skipping broken JSON file {file_path.name}: {e}")
            return

        modified = False

        if isinstance(data, dict) and data.get("canary") == canary:
            del data["canary"]
            modified = True

        elif isinstance(data, list) and data and isinstance(data[-1], dict) and data[-1].get("canary") == canary:
            del data[-1]["canary"]
            modified = True

        if modified:
            f.seek(0)
            json.dump(data, f, indent=2)
            f.truncate()
            if verbose:
                logger.info(f"Removed canary from {file_path.name}")

    elif suffix == ".jsonl":
        import re

        lines = file_path.read_text(encoding="utf-8").splitlines()
        # Use both the old and new embedded canary patterns
        embedded_patterns = [
            r"CANARY_EMBEDDED: CANARY_DATASET_[A-Z_]+_[a-f0-9]{64}_[0-9]+",
            r"CANARY_EMBEDDED: CANARY_DATASET_[a-f0-9]{64}_[0-9]+",
        ]
        # Remove lines matching any embedded canary pattern (anywhere in the line)
        cleaned_lines = [line for line in lines if not any(re.search(pattern, line) for pattern in embedded_patterns)]

        def clean_json_obj(obj):
            if isinstance(obj, dict):
                # Remove keys whose value is exactly the canary string
                return {k: clean_json_obj(v) for k, v in obj.items() if v != canary}
            elif isinstance(obj, list):
                return [clean_json_obj(v) for v in obj]
            elif isinstance(obj, str):
                return obj.replace(canary, "")
            else:
                return obj

        final_lines = []
        for line in cleaned_lines:
            try:
                obj = json.loads(line)
                cleaned_obj = clean_json_obj(obj)
                final_lines.append(json.dumps(cleaned_obj, ensure_ascii=False))
            except Exception:
                final_lines.append(line)

        file_path.write_text("\n".join(final_lines) + "\n", encoding="utf-8")
        if verbose:
            logger.info(f"Removed canary from {file_path.name}")


def insert_canaries_into_files(directory: str, canary: str, verbose: bool = False) -> None:
    for file_path in Path(directory).rglob("*"):
        if file_path.suffix in [".txt", ".html", ".json", ".jsonl"]:
            insert_canary_into_file(file_path, canary, verbose)


def remove_canaries_from_files(directory: str, canary: str, verbose: bool = False) -> None:
    for file_path in Path(directory).rglob("*"):
        if file_path.suffix in [".txt", ".html", ".json", ".jsonl"]:
            if verbose:
                logger.info(f"Removing canary from {file_path.name}")
            remove_canary_from_file(file_path, canary, verbose)


def validate_json_files(directory: str, verbose: bool = False) -> None:
    """
    Validate all JSON and JSONL files in a directory.
    Raises ValueError if any files are malformed.
    """
    errors = []

    for file_path in Path(directory).rglob("*"):
        if file_path.suffix == ".json":
            try:
                with open(file_path, "r", encoding="utf-8") as f:
                    json.load(f)
            except json.JSONDecodeError as e:
                error_msg = f"Malformed JSON in {file_path}: {e}"
                if verbose:
                    logger.warning(f"❌ {error_msg}")
                errors.append(error_msg)

        elif file_path.suffix == ".jsonl":
            try:
                lines = file_path.read_text(encoding="utf-8").strip().splitlines()
                for i, line in enumerate(lines, 1):
                    if line.strip():  # Skip empty lines
                        json.loads(line)
            except json.JSONDecodeError as e:
                error_msg = f"Malformed JSONL in {file_path} at line {i}: {e}"
                if verbose:
                    logger.warning(f"❌ {error_msg}")
                errors.append(error_msg)

    if errors:
        raise ValueError(f"Found {len(errors)} malformed JSON/JSONL files:\n" + "\n".join(errors))
