from pathlib import Path
from typing import Union, Optional, List, Dict, Set, Generator
import ast
import sys
import shutil
import hashlib
import zipfile
import tarfile
from concurrent.futures import ThreadPoolExecutor
import mmap

from ..ImporterException import ASTError, MoveError

# Global configuration
encoding = sys.getfilesystemencoding()
DEFAULT_CHUNK_SIZE = 1_000_000
MAX_WORKERS = 4
ZIP_TYPES = {
    "zip:def",
    "zip:std",
    "zip:bz2",
    "zip:lzma",
    "tar",
    "tar:xz",
    "tar:bz2",
    "tar:gz",
}


def rmempty(ListObject):
    return [value for value in ListObject if value.strip()]


def __paths__(
    path: str,
    type: Optional[str] = None,
    with_path: bool = False,
    ignore: Optional[List[str]] = None,
) -> List[str]:
    """
    Recursively retrieve files or directories from a given path with optimized performance.

    Uses iterative scanning with early filtering for better memory efficiency and performance
    with large directory structures.

    Args:
        path (str): Root directory to scan
        type (str, optional): Filter by type - "file" for files, "fold" for directories
        with_path (bool): If True, return full paths, otherwise return names only
        ignore (List[str], optional): List of file extensions to ignore

    Returns:
        List[str]: List of file/directory paths or names

    Example:
        >>> __paths__("/project", type="file", with_path=True)
        ['/project/main.py', '/project/utils/helper.py']
    """
    path = Path(path)
    if ignore is None:
        ignore = []

    # Convert ignore extensions to set for O(1) lookups
    ignore_set = set(ignore)

    try:
        if type == "file":
            items = []
            # Use rglob with pattern for efficient filtering
            for file_path in path.rglob("*"):
                if file_path.is_file() and file_path.suffix not in ignore_set:
                    items.append(str(file_path) if with_path else file_path.name)
            return items

        elif type == "fold":
            return [
                str(dir_path) if with_path else dir_path.name
                for dir_path in path.rglob("*")
                if dir_path.is_dir()
            ]
        else:
            return []

    except (PermissionError, OSError) as e:
        # Log and return empty list on permission errors
        return []


def __load__(path: str, chunksize: int = DEFAULT_CHUNK_SIZE) -> Optional[str]:
    """
    Efficiently load file content with memory-mapped reading for large files.

    Uses memory mapping for optimal performance with large files and provides
    automatic encoding fallback handling.

    Args:
        path (str): Path to the file to load
        chunksize (int): Size of chunks for reading (default: 1MB)

    Returns:
        Optional[str]: File content as string, or None on failure

    Raises:
        FileNotFoundError: If file doesn't exist
        PermissionError: If read permissions are insufficient

    Example:
        >>> content = __load__("/path/to/file.py")
        'def example(): ...'
    """
    path_obj = Path(path)

    if not path_obj.exists():
        raise FileNotFoundError(f"File not found: {path}")
    if not path_obj.is_file():
        raise ValueError(f"Path is not a file: {path}")

    try:
        # Use memory mapping for large files
        if path_obj.stat().st_size > chunksize * 10:  # For files > 10MB
            with open(path, "rb") as f:
                with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
                    try:
                        return mm.read().decode(encoding)
                    except UnicodeDecodeError:
                        # Fallback to binary read with error replacement
                        return mm.read().decode(encoding, errors="replace")

        # Standard reading for smaller files
        with open(path, "r", encoding=encoding) as f:
            return f.read()

    except UnicodeDecodeError:
        # Fallback to binary reading
        try:
            with open(path, "rb") as f:
                content = f.read()
                return content.decode(encoding, errors="replace")
        except Exception as e:
            raise ValueError(f"Failed to decode file {path}: {e}")
    except Exception as e:
        raise IOError(f"Error reading file {path}: {e}")


def isdunder(function: str) -> bool:
    """Check if function is magic function"""
    return function.startswith("__") and function.endswith("__")


def __imports__(
    code: str, errors: bool = True
) -> Dict[str, Union[Set[str], Dict[str, Set[str]]]]:
    """
    Parse Python source code and extract import statements with optimized AST walking.

    Uses single-pass AST traversal with early node type checking for maximum performance.
    Handles both 'import' and 'from ... import' statements.

    Args:
        code (str): Python source code to parse
        errors (bool): If True, raises syntax errors, otherwise returns empty result

    Returns:
        Dict with keys:
            - 'import': Set of directly imported module names
            - 'from': Dict mapping modules to sets of imported names

    Example:
        >>> __imports__('import os, sys; from json import loads, dumps')
        {
            'import': {'os', 'sys'},
            'from': {'json': {'loads', 'dumps'}}
        }
    """
    if not code or not code.strip():
        return {"import": set(), "from": {}}

    try:
        tree = ast.parse(code)
    except SyntaxError as e:
        if errors:
            raise ASTError(f"Syntax error in code: {e}") from e
        return {"import": set(), "from": {}}

    result = {"import": set(), "from": {}}

    # Single pass through all nodes with optimized type checking
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            # Handle standard imports: import os, sys
            for alias in node.names:
                result["import"].add(alias.name)

        elif isinstance(node, ast.ImportFrom):
            # Handle from imports: from module import name1, name2
            module_name = node.module or ""
            if module_name not in result["from"]:
                result["from"][module_name] = set()

            for alias in node.names:
                result["from"][module_name].add(alias.name)

    return result


def __text__(path: str) -> Union[str, bytes]:
    """
    Robust file content reader with comprehensive error handling and encoding fallbacks.

    Attempts multiple decoding strategies and provides detailed error information
    for troubleshooting file encoding issues.

    Args:
        path (str): Path to the file to read

    Returns:
        Union[str, bytes]: File content as string (if decodable) or bytes

    Raises:
        FileNotFoundError: If file doesn't exist
        PermissionError: If read permissions are insufficient
        IOError: For other I/O related errors

    Example:
        >>> content = __text__("/path/to/file.txt")
        'File content here...'
    """
    path_obj = Path(path)

    if not path_obj.exists():
        raise FileNotFoundError(f"File not found: {path}")
    if not path_obj.is_file():
        raise ValueError(f"Path is not a file: {path}")

    # Try UTF-8 first (most common)
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except UnicodeDecodeError:
        pass

    # Try system encoding
    try:
        with open(path, "r", encoding=encoding) as f:
            return f.read()
    except UnicodeDecodeError:
        pass

    # Fallback to binary with encoding detection
    try:
        with open(path, "rb") as f:
            content = f.read()

        # Try common encodings
        for enc in ["utf-8", "latin-1", "cp1252", encoding]:
            try:
                return content.decode(enc)
            except UnicodeDecodeError:
                continue

        # Return as bytes if no encoding works
        return content

    except PermissionError as e:
        raise PermissionError(f"Permission denied reading file: {path}") from e
    except Exception as e:
        raise IOError(f"Error reading file {path}: {e}")


def __un__(
    load: str, astype: type, errors: bool = True, dunder: bool = True
) -> List[str]:
    """
    Extract names of AST nodes of specified type from Python source code.

    Optimized AST parser for specific node types with filtering options.
    Used as base function for __defs__ and __classes__.

    Args:
        load (str): Python source code to parse
        astype (type): AST node type to extract (e.g., ast.FunctionDef, ast.ClassDef)
        errors (bool): If True, raises syntax errors, otherwise returns empty list
        dunder (bool): If False, excludes dunder (__xxx__) methods

    Returns:
        List[str]: Names of found nodes

    Raises:
        TypeError: If load is not a string
        ASTError: If code has syntax errors and errors=True

    Example:
        >>> __un__('def foo(): pass', ast.FunctionDef)
        ['foo']
    """
    if not isinstance(load, str):
        raise TypeError(f"load must be str, not {type(load).__name__}")

    if not load.strip():
        return []

    try:
        tree = ast.parse(load)
    except SyntaxError as e:
        if errors:
            raise ASTError(f"Syntax error in code: {e}") from e
        return []

    names = []
    # Single pass with specific type checking
    for node in ast.walk(tree):
        if isinstance(node, astype):
            names.append(node.name)

    # Filter dunder methods if requested
    if not dunder:
        return [name for name in names if not isdunder(name)]

    return names


def __defs__(load: str, errors: bool = True, dunder: bool = True) -> List[str]:
    """
    Extract function definitions from Python source code.

    Wrapper around __un__ specifically for function definitions (ast.FunctionDef).

    Args:
        load (str): Python source code to parse
        errors (bool): If True, raises syntax errors
        dunder (bool): If False, excludes dunder methods

    Returns:
        List[str]: Names of function definitions

    Example:
        >>> __defs__('def foo(): pass\\ndef bar(): pass')
        ['foo', 'bar']
    """
    return __un__(load, ast.FunctionDef, errors, dunder)


def __classes__(load: str, errors: bool = True) -> List[str]:
    """
    Extract class definitions from Python source code.

    Wrapper around __un__ specifically for class definitions (ast.ClassDef).

    Args:
        load (str): Python source code to parse
        errors (bool): If True, raises syntax errors

    Returns:
        List[str]: Names of class definitions

    Example:
        >>> __classes__('class Foo:\\n    pass')
        ['Foo']
    """
    return __un__(
        load, ast.ClassDef, errors, dunder=True
    )  # Always include dunder for classes


def __addef__(
    savein: List,
    addin: str,
    targetype: str,
    name: Optional[bool] = None,
    errors: bool = True,
    dunder: bool = True,
) -> None:
    """
    Append definitions from a file to a results list.

    Helper function for __typedef__ that processes individual files and
    appends results to the accumulating list.

    Args:
        savein (List): List to append results to
        addin (str): File path to process
        targetype (str): Type of definitions to extract - "defs" or "classes"
        name (bool, optional): If True, include filename with results
        errors (bool): If True, raises processing errors
        dunder (bool): If False, excludes dunder methods

    Returns:
        None: Results are appended to savein parameter
    """
    try:
        content = __load__(addin)
        if not content:
            return

        if targetype == "defs":
            definitions = __defs__(content, errors, dunder)
        elif targetype == "classes":
            definitions = __classes__(content, errors)
        else:
            return

        if definitions:
            if name:
                filename = Path(addin).name
                savein.append((filename, definitions))
            else:
                savein.extend(definitions)

    except Exception as e:
        if errors:
            raise
        # Silently skip files that can't be processed when errors=False

import re
from pathlib import Path
from typing import Optional, List, Tuple, Union
from concurrent.futures import ThreadPoolExecutor, as_completed
import mmap
import time

# Global configuration
MAX_WORKERS = 8
CHUNK_SIZE = 8192  # Smaller chunks for better memory usage

# Optimized regex patterns for function and class detection
FUNCTION_PATTERNS = [
    # Standard function definition
    re.compile(r'^\s*def\s+(\w+)\s*\(', re.MULTILINE),
    # Async function
    re.compile(r'^\s*async\s+def\s+(\w+)\s*\(', re.MULTILINE),
]

CLASS_PATTERNS = [
    # Standard class definition
    re.compile(r'^\s*class\s+(\w+)\s*[:\(]', re.MULTILINE),
]

# Advanced patterns for complex cases
ADVANCED_FUNCTION_PATTERNS = [
    # Function with type hints
    re.compile(r'^\s*def\s+(\w+)\s*\(\s*[^)]*\s*\)\s*->\s*[^:]+:', re.MULTILINE),
    # Function with decorators
    re.compile(r'^\s*@\w+(\.\w+)*\s*\n\s*def\s+(\w+)\s*\(', re.MULTILINE),
    # Multiple decorators
    re.compile(r'^(?:\s*@\w+(\.\w+)*\s*\n)+\s*def\s+(\w+)\s*\(', re.MULTILINE),
]

COMMENT_PATTERN = re.compile(r'#.*?$|(\'\'\'[\s\S]*?\'\'\'|\"\"\"[\s\S]*?\"\"\")', re.MULTILINE)
STRING_PATTERN = re.compile(r'(\'[^\']*\'|\"[^\"]*\")', re.MULTILINE)

def __fast_parse_definitions__(content: str, target_type: str, dunder: bool = True) -> List[str]:
    """
    Ultra-fast parsing of Python definitions using regex and string scanning.
    Bypasses AST completely for massive performance gains.
    
    Args:
        content: Python source code
        target_type: 'defs' for functions, 'classes' for classes
        dunder: Whether to include dunder methods
    
    Returns:
        List of definition names
    """
    if not content:
        return []
    
    # Remove comments and strings to avoid false positives
    content = __remove_comments_and_strings__(content)
    
    definitions = []
    patterns = FUNCTION_PATTERNS if target_type == 'defs' else CLASS_PATTERNS
    
    for pattern in patterns:
        matches = pattern.findall(content)
        for match in matches:
            # Handle different regex group structures
            if isinstance(match, tuple):
                name = match[-1]  # Get last group which should be the name
            else:
                name = match
            
            if name and (dunder or not isdunder(name)):
                definitions.append(name)
    
    # Remove duplicates while preserving order
    seen = set()
    return [x for x in definitions if not (x in seen or seen.add(x))]

def __remove_comments_and_strings__(content: str) -> str:
    """
    Remove comments and string literals from Python code to avoid false positives.
    Uses iterative processing for better performance.
    """
    # Remove block comments first
    content = re.sub(COMMENT_PATTERN, '', content)
    
    # Remove simple strings (basic approach for performance)
    lines = []
    in_multiline_string = False
    multiline_delimiter = None
    
    for line in content.split('\n'):
        if in_multiline_string:
            # Check for end of multiline string
            if multiline_delimiter in line:
                end_idx = line.find(multiline_delimiter)
                line = line[end_idx + 3:]
                in_multiline_string = False
                multiline_delimiter = None
            else:
                continue  # Skip entire line inside multiline string
        
        # Check for multiline string start
        triple_quotes = ["'''", '"""']
        for quote in triple_quotes:
            if quote in line:
                start_idx = line.find(quote)
                # Check if it's not an empty string
                if start_idx + 3 < len(line) and line[start_idx + 3] != quote[0]:
                    in_multiline_string = True
                    multiline_delimiter = quote
                    line = line[:start_idx]
                    break
        
        # Remove inline strings (simplified)
        line = re.sub(STRING_PATTERN, '""', line)
        
        if line.strip():
            lines.append(line)
    
    return '\n'.join(lines)

def __optimized_file_reader__(file_path: str, max_size: int = 10 * 1024 * 1024) -> str:
    """
    Ultra-fast file reader with smart chunking and encoding detection.
    """
    try:
        file_size = Path(file_path).stat().st_size
        
        if file_size > max_size:
            # For large files, read only the beginning (where definitions usually are)
            with open(file_path, 'rb') as f:
                # Read first 100KB which typically contains most definitions
                chunk = f.read(100 * 1024)
                return chunk.decode('utf-8', errors='ignore')
        
        # For smaller files, use memory mapping
        with open(file_path, 'rb') as f:
            with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
                return mm.read().decode('utf-8', errors='ignore')
                
    except Exception:
        return ""

def __process_single_file__(
    file_path: str, 
    target_type: str, 
    include_filename: bool,
    dunder: bool
) -> Union[List[str], Tuple[str, List[str]]]:
    """
    Process a single file and return its definitions.
    """
    content = __optimized_file_reader__(file_path)
    if not content:
        return [] if not include_filename else (Path(file_path).name, [])
    
    definitions = __fast_parse_definitions__(content, target_type, dunder)
    
    if include_filename:
        return (Path(file_path).name, definitions)
    else:
        return definitions

def __typedef__(
    target: Optional[str] = None,
    maxfiles: Optional[int] = None,
    name: bool = False,
    targetype: Optional[str] = None,
    files: Optional[List[str]] = None,
    errors: bool = True,
    dunder: bool = True,
    batch_size: int = 50
) -> List[str]:
    """
    Ultra-fast definition extractor with parallel processing and regex-based parsing.
    
    Features:
    - Parallel file processing
    - file reading with chunking
    - pattern matching
    - Memory efficient
    
    Args:
        target: Specific filename to process
        maxfiles: Maximum number of files to process
        name: If True, returns tuples of (filename, definitions)
        targetype: 'defs' for functions, 'classes' for classes
        files: List of file paths to process
        errors: If True, raises errors during processing
        dunder: If False, excludes dunder methods
        batch_size: Number of files to process in each batch
    
    Returns:
        List of definitions or (filename, definitions) tuples
    """
    if not files or targetype not in ["defs", "classes"]:
        return []
    
    # Apply filters
    if target:
        target_lower = target.lower()
        files = [f for f in files if Path(f).name.lower() == target_lower]
    
    if maxfiles and maxfiles > 0:
        files = files[:maxfiles]
    
    if not files:
        return []
    
    results = []
    
    # Process files in parallel batches for better memory usage
    for i in range(0, len(files), batch_size):
        batch = files[i:i + batch_size]
        
        with ThreadPoolExecutor(max_workers=min(MAX_WORKERS, len(batch))) as executor:
            future_to_file = {
                executor.submit(
                    __process_single_file__, 
                    file_path, 
                    targetype, 
                    name, 
                    dunder
                ): file_path 
                for file_path in batch
            }
            
            for future in as_completed(future_to_file):
                try:
                    result = future.result()
                    if result:
                        if name and isinstance(result, tuple) and result[1]:
                            results.append(result)
                        elif not name and result:
                            results.extend(result)
                except Exception as e:
                    if errors:
                        raise
                    # Silently continue on error when errors=False
    
    # Post-process results
    if name:
        # Filter out empty results
        return [(fname, defs) for fname, defs in results if defs]
    else:
        # Remove duplicates and empty strings
        seen = set()
        unique_results = []
        for item in results:
            if item and item not in seen:
                seen.add(item)
                unique_results.append(item)
        return unique_results

# Additional optimized utility functions
def __bulk_typedef__(
    directories: List[str],
    targetype: str,
    file_pattern: str = "*.py",
    name: bool = False,
    dunder: bool = True
) -> List[str]:
    """
    Bulk process multiple directories for definitions extraction.
    """
    all_files = []
    for directory in directories:
        path = Path(directory)
        if path.exists() and path.is_dir():
            all_files.extend(str(p) for p in path.rglob(file_pattern))
    
    return __typedef__(
        targetype=targetype,
        files=all_files,
        name=name,
        dunder=dunder
    )

def __typedef_stats__(
    files: List[str],
    targetype: str
) -> dict:
    """
    Get statistics about definitions in files.
    """
    start_time = time.time()
    definitions = __typedef__(targetype=targetype, files=files)
    processing_time = time.time() - start_time
    
    return {
        'total_files': len(files),
        'total_definitions': len(definitions),
        'processing_time_seconds': round(processing_time, 3),
        'files_per_second': round(len(files) / processing_time, 2) if processing_time > 0 else 0,
        'definitions_per_second': round(len(definitions) / processing_time, 2) if processing_time > 0 else 0
    }

def __replace__(path: str, oldContent: str, newContent: str) -> str:
    """
    Replace content in a file and return the modified content.

    Performs in-memory replacement and writes back to file atomically
    to prevent data corruption.

    Args:
        path (str): Path to the file to modify
        oldContent (str): Content to be replaced
        newContent (str): New content to replace with

    Returns:
        str: The modified file content

    Raises:
        FileNotFoundError: If file doesn't exist
        IOError: If file can't be read or written

    Example:
        >>> __replace__("/path/file.txt", "old", "new")
        'File with new content...'
    """
    content = __load__(path)
    new_content = content.replace(oldContent, newContent)

    # Atomic write to prevent corruption
    temp_path = Path(path).with_suffix(".tmp")
    try:
        __dump__(temp_path, new_content)
        temp_path.replace(path)  # Atomic replace
    except Exception as e:
        if temp_path.exists():
            temp_path.unlink()
        raise IOError(f"Failed to replace content in {path}: {e}")

    return new_content


def __move__(src: str, dest: str) -> None:
    """
    Move file or directory to destination with comprehensive error handling.

    Uses shutil.move with proper path handling and provides detailed error
    information for troubleshooting.

    Args:
        src (str): Source path to move
        dest (str): Destination directory

    Returns:
        None

    Raises:
        MoveError: If move operation fails
        FileNotFoundError: If source doesn't exist

    Example:
        >>> __move__("source.txt", "/destination/")
    """
    psrc = Path(src)
    pdest = Path(dest)

    if not psrc.exists():
        raise FileNotFoundError(f"Source not found: {src}")

    try:
        # Ensure destination directory exists
        pdest.mkdir(parents=True, exist_ok=True)
        shutil.move(str(psrc), str(pdest / psrc.name))
    except Exception as e:
        raise MoveError(f"Failed to move {src} to {dest}: {e}") from e


def __dump__(
    file: Union[str, Path],
    content: str,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    encoding: str = encoding,
    path: str = ".",
) -> None:
    """
    Write content to file with chunked writing for memory efficiency.

    Supports large content writing through chunked operations and
    provides atomic write semantics to prevent partial writes.

    Args:
        file (Union[str, Path]): Filename or Path object to write to
        content (str): Content to write
        chunk_size (int): Size of write chunks (default: 1MB)
        encoding (str): Text encoding to use
        path (str): Directory path for the file

    Returns:
        None

    Raises:
        IOError: If write operation fails

    Example:
        >>> __dump__("output.txt", "Hello World")
    """
    fname = Path(path) / Path(file)

    # Ensure directory exists
    fname.parent.mkdir(parents=True, exist_ok=True)

    try:
        with open(fname, "w", encoding=encoding) as f:
            if chunk_size and len(content) > chunk_size:
                # Write in chunks for large content
                for i in range(0, len(content), chunk_size):
                    f.write(content[i : i + chunk_size])
            else:
                f.write(content)
    except Exception as e:
        raise IOError(f"Failed to write file {fname}: {e}")


def __append__(
    file: str,
    content: str,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    encoding: str = encoding,
) -> None:
    """
    Append content to existing file with chunked writing support.

    Efficiently appends content to files of any size using chunked operations
    to maintain low memory footprint.

    Args:
        file (str): Path to file to append to
        content (str): Content to append
        chunk_size (int): Size of write chunks
        encoding (str): Text encoding to use

    Returns:
        None

    Raises:
        IOError: If append operation fails

    Example:
        >>> __append__("log.txt", "New log entry\\n")
    """
    try:
        with open(file, "a", encoding=encoding) as f:
            if chunk_size and len(content) > chunk_size:
                for i in range(0, len(content), chunk_size):
                    f.write(content[i : i + chunk_size])
            else:
                f.write(content)
    except Exception as e:
        raise IOError(f"Failed to append to file {file}: {e}")


def __hashfile__(
    file: str, algo: str = "sha256", chunk_size: int = DEFAULT_CHUNK_SIZE
) -> str:
    """
    Calculate cryptographic hash of file content with efficient chunked reading.

    Supports multiple hash algorithms and handles large files efficiently
    through chunked reading to maintain low memory usage.

    Args:
        file (str): Path to file to hash
        algo (str): Hash algorithm (md5, sha1, sha256, sha512, etc.)
        chunk_size (int): Size of read chunks

    Returns:
        str: Hexadecimal digest of file hash

    Raises:
        FileNotFoundError: If file doesn't exist
        ValueError: If algorithm is not supported

    Example:
        >>> __hashfile__("/path/to/file.iso", "sha256")
        'a1b2c3d4e5f6...'
    """
    if not Path(file).exists():
        raise FileNotFoundError(f"File not found: {file}")

    try:
        hash_func = hashlib.new(algo)
    except ValueError as e:
        raise ValueError(f"Unsupported hash algorithm: {algo}") from e

    with open(file, "rb") as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            hash_func.update(chunk)

    return hash_func.hexdigest()


def list_path(path: str, names: bool = False) -> List[Union[Path, str]]:
    """
    List contents of a directory with optional name-only output.

    Efficient directory listing with simple filtering options.

    Args:
        path (str): Directory path to list
        names (bool): If True, return only names, otherwise return Path objects

    Returns:
        List[Union[Path, str]]: Directory contents

    Example:
        >>> list_path("/project/src", names=True)
        ['main.py', 'utils', 'README.md']
    """
    path_obj = Path(path)
    if not path_obj.exists() or not path_obj.is_dir():
        return []

    return [p.name if names else p for p in path_obj.glob("*")]


def make(filename: str) -> str:
    """
    Create an empty file and return the filename.

    Simple utility function for creating empty files with proper
    directory creation and error handling.

    Args:
        filename (str): Name/path of file to create

    Returns:
        str: The created filename

    Raises:
        IOError: If file creation fails

    Example:
        >>> make("new_file.txt")
        'new_file.txt'
    """
    path = Path(filename)
    path.parent.mkdir(parents=True, exist_ok=True)

    try:
        path.touch()
        return filename
    except Exception as e:
        raise IOError(f"Failed to create file {filename}: {e}")


def _pres_(file: Path, suffix: str, mode: str) -> None:
    """
    Internal helper for creating tar archives with specified compression.

    Args:
        file (Path): File to archive
        suffix (str): Suffix to add to archive name
        mode (str): Tarfile mode string
    """
    archive_name = file.name + suffix
    with tarfile.open(archive_name, mode) as tar:
        tar.add(file)


def _unpres_(file: Path, del_suff: Optional[str] = None, mode: str = "r") -> None:
    """
    Internal helper for extracting tar archives.

    Args:
        file (Path): Archive file to extract
        del_suff (str, optional): Suffix to remove from extracted path
        mode (str): Tarfile mode string
    """
    extract_path = (
        file.with_suffix("") if del_suff and file.suffix == del_suff else file
    )
    with tarfile.open(file, mode) as tar:
        tar.extractall(extract_path.parent)

def __compress__(file: str, mode_zip: str) -> str: 
    """
    Compress file using various archive formats with comprehensive format support.

    Args:
        file (str): Path to file to compress
        mode_zip (str): Compression mode

    Returns:
        str: Path to the created archive file 
    """
    file_path = Path(file)
    if not file_path.exists():
        raise FileNotFoundError(f"File not found: {file}")

    mode = mode_zip.lower()

    if mode.startswith("zip"):
        zip_types = {
            "zip:lzma": zipfile.ZIP_LZMA,
            "zip:def": zipfile.ZIP_DEFLATED,
            "zip:bz2": zipfile.ZIP_BZIP2,
            "zip:std": zipfile.ZIP_STORED,
        }

        if mode not in zip_types:
            raise ValueError(f"Unsupported ZIP mode: {mode_zip}")

        archive_name = file_path.name + ".zip"
        with zipfile.ZipFile(archive_name, "w", zip_types[mode]) as zipf:
            zipf.write(file_path, file_path.name)
        
        return archive_name  

    elif mode.startswith("tar"):
        tar_modes = {
            "tar": ("w", ".tar"),
            "tar:xz": ("w:xz", ".tar.xz"),
            "tar:gz": ("w:gz", ".tar.gz"),
            "tar:bz2": ("w:bz2", ".tar.bz2"),
        }

        if mode not in tar_modes:
            raise ValueError(f"Unsupported TAR mode: {mode_zip}")

        archive_mode, suffix = tar_modes[mode]
        archive_name = file_path.name + suffix
        _pres_(file_path, suffix, archive_mode)
        
        return archive_name  

    else:
        raise ValueError(f"Unsupported compression mode: {mode_zip}")

def __decompress__(file: str) -> Path:
    """
    Decompress archive file and return path to extracted content.

    Supports multiple archive formats with automatic format detection
    and handling of both single-file and multi-file archives.

    Args:
        file (str): Path to archive file to decompress

    Returns:
        Path: Path to extracted content

    Raises:
        FileNotFoundError: If archive doesn't exist
        ValueError: If archive format is not supported

    Example:
        >>> __decompress__("archive.zip")
        Path('archive')
    """
    file_path = Path(file)
    if not file_path.exists():
        raise FileNotFoundError(f"Archive not found: {file}")

    # Handle ZIP files
    if zipfile.is_zipfile(file_path):
        extract_path = file_path.with_suffix("")

        with zipfile.ZipFile(file_path, "r") as archive:
            file_list = archive.namelist()

            if len(file_list) > 1:
                # Multi-file archive
                archive.extractall(extract_path)
            else:
                # Single-file archive
                extracted = archive.extract(file_list[0])
                Path(extracted).rename(extract_path)

        # Remove original archive
        file_path.unlink()
        return extract_path

    # Handle TAR files
    elif file_path.suffix in [".tar", ".gz", ".bz2", ".xz"]:
        mode = "r"
        if file_path.suffix == ".gz":
            mode = "r:gz"
        elif file_path.suffix == ".bz2":
            mode = "r:bz2"
        elif file_path.suffix == ".xz":
            mode = "r:xz"

        extract_path = (
            file_path.with_suffix("").with_suffix("")
            if file_path.suffix in [".gz", ".bz2", ".xz"]
            else file_path.with_suffix("")
        )
        _unpres_(file_path, file_path.suffix, mode)
        return extract_path

    else:
        raise ValueError(f"Unsupported archive format: {file_path.suffix}")


def tools_module() -> List[str]:
    """
    Return standard Python project file templates.

    Provides list of common files needed for Python package distribution
    and development.

    Returns:
        List[str]: List of standard project file names

    Example:
        >>> tools_module()
        ['setup.py', 'LICENSE', 'pyproject.toml', 'README.md']
    """
    return ["setup.py", "LICENSE", "pyproject.toml", "README.md"]
