
from __future__ import annotations
from pathlib import Path
import shutil
import zipfile
import tarfile

ARCHIVE_EXTS = {'.zip', '.rar', '.7z', '.tar', '.tgz', '.gz', '.bz2', '.xz'}


def safe_extract_zip(zip_path: Path, out_dir: Path):
    out_dir.mkdir(parents=True, exist_ok=True)
    with zipfile.ZipFile(zip_path) as z:
        base = out_dir.resolve()
        for info in z.infolist():
            target = (out_dir / info.filename).resolve()
            if not str(target).startswith(str(base)):
                raise RuntimeError('Unsafe zip path detected')
        z.extractall(out_dir)


def extract_archive(archive, out_dir):
    archive = Path(archive)
    out_dir = Path(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    ext = archive.suffix.lower()
    if ext == '.zip':
        safe_extract_zip(archive, out_dir)
    elif ext == '.rar':
        try:
            import rarfile
            with rarfile.RarFile(str(archive)) as r:
                r.extractall(str(out_dir))
        except Exception as e:
            raise RuntimeError('RAR requires python package rarfile and system unrar/bsdtar. Install them or convert archive to ZIP.') from e
    elif ext == '.7z':
        try:
            import py7zr
            with py7zr.SevenZipFile(str(archive), mode='r') as z:
                z.extractall(str(out_dir))
        except Exception as e:
            raise RuntimeError('7Z requires optional package py7zr. Install it or convert archive to ZIP.') from e
    elif ext in {'.tar', '.tgz', '.gz', '.bz2', '.xz'} or ''.join(archive.suffixes[-2:]).lower() in {'.tar.gz', '.tar.bz2', '.tar.xz'}:
        try:
            shutil.unpack_archive(str(archive), str(out_dir))
        except Exception:
            with tarfile.open(str(archive)) as t:
                t.extractall(str(out_dir))
    else:
        # Try shutil registry for other archive formats installed in the environment.
        try:
            shutil.unpack_archive(str(archive), str(out_dir))
        except Exception as e:
            raise RuntimeError(f'Unsupported archive type: {archive.name}') from e
    return [p for p in out_dir.rglob('*') if p.is_file()]
