# -*- coding: utf-8 -*-
# make.py
# WinPython build script
# Copyright © 2012 Pierre Raybaut
# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
# Licensed under the terms of the MIT License
# (see wppm/__init__.py for details)

import os
import re
import shutil
from pathlib import Path
from wppm import wppm, utils

PORTABLE_DIRECTORY = Path(__file__).parent / "portable"
assert PORTABLE_DIRECTORY.is_dir(), f"Portable directory not found: {PORTABLE_DIRECTORY}"

def copy_items(source_directories: list[Path], target_directory: Path, verbose: bool = False):
    """Copies items from source directories to the target directory."""
    target_directory.mkdir(parents=True, exist_ok=True)
    for source_dir in source_directories:
        if not source_dir.is_dir():
            print(f"Warning: Source directory not found: {source_dir}")
            continue
        for source_item in source_dir.iterdir():
            target_item = target_directory / source_item.name
            copy_function = shutil.copytree if source_item.is_dir() else shutil.copy2
            try:
                copy_function(source_item, target_item)
                if verbose:
                    print(f"Copied: {source_item} -> {target_item}")
            except Exception as e:
                print(f"Error copying {source_item} to {target_item}: {e}")

def parse_list_argument(argument_value: str | list[str], separator=" ") -> list[str]:
    """Parse a separated list argument into a list of strings."""
    if not argument_value: return []
    return argument_value.split(separator) if isinstance(argument_value, str) else list(argument_value)

class WinPythonDistributionBuilder:
    """Builds a WinPython distribution."""

    def __init__(self, build_number: int, release_level: str, basedir_wpy: Path,
                 source_dirs: Path, tools_directories: list[Path] = None,
                 verbose: bool = False, flavor: str = "",
                 winpyver: str = "", winpyver2: str = ""):
        """
        Initializes the WinPythonDistributionBuilder.
        Args:
            build_number: The build number (integer).
            release_level: The release level (e.g., "beta", "").
            basedir_wpy:  top directory of the build (c:\...\Wpy...)
            source_dirs: Directory containing wheel files for packages.
            tools_directories: List of directories containing development tools to include.
            verbose: Enable verbose output.
            flavor: WinPython flavor (e.g., "Barebone").
        """
        self.build_number = build_number
        self.release_level = release_level
        self.winpython_directory = Path(basedir_wpy)
        self.target_directory = self.winpython_directory.parent
        self.source_dirs = Path(source_dirs)
        self.tools_directories = tools_directories or []
        self.verbose = verbose
        self.distribution: wppm.Distribution | None = None
        self.flavor = flavor
        self.python_zip_file: Path = self._get_python_zip_file()
        self.python_name = self.python_zip_file.stem
        self.python_directory_name = "python"
        self.winpyver = winpyver
        self.winpyver2 = winpyver2

    def _get_python_zip_file(self) -> Path:
        """Finds the Python .zip file in the wheels directory."""
        for source_item in self.source_dirs.iterdir():
            if re.match(r"(pypy3|python-).*\.zip", source_item.name):
                return source_item
        raise RuntimeError(f"Could not find Python zip package in {self.source_dirs}")

    @property
    def winpython_version_name(self) -> str:
        """Returns the full WinPython version string."""
        return f"{self.python_full_version}.{self.build_number}{self.flavor}{self.release_level}"

    @property
    def python_full_version(self) -> str:
        """Retrieves the Python full version string from the distribution."""
        return utils.get_python_long_version(self.distribution.target) if self.distribution else "0.0.0"

    def _print_action(self, text: str):
        """Prints an action message with progress indicator."""
        utils.print_box(text) if self.verbose else print(f"{text}...", end="", flush=True)

    def _extract_python_archive(self):
        """Extracts the Python zip archive to create the base Python environment."""
        self._print_action("Extracting Python archive")
        utils.extract_archive(self.python_zip_file, self.winpython_directory)
        # Relocate to /python subfolder if needed (for newer structure) #2024-12-22 to /python
        expected_python_directory = self.winpython_directory / self.python_directory_name
        if self.python_directory_name != self.python_name and not expected_python_directory.is_dir():
            os.rename(self.winpython_directory / self.python_name, expected_python_directory)

    def _copy_essential_files(self):
        """Copies pre-made objects"""
        self._print_action("Copying launchers")
        copy_items([PORTABLE_DIRECTORY / "launchers_final"], self.winpython_directory, self.verbose)

        tools_target_directory = self.winpython_directory / "t"
        self._print_action(f"Copying tools to {tools_target_directory}")
        copy_items(self.tools_directories, tools_target_directory, self.verbose)

    def _create_env_config(self):
        """Creates environment setup"""
        executable_name = self.distribution.short_exe if self.distribution else "python.exe"
        config = {
            "WINPYthon_exe": executable_name,
            "WINPYthon_subdirectory_name": self.python_directory_name,
            "WINPYVER": self.winpyver,  # self.winpython_version_name,
            "WINPYVER2": self.winpyver2,  # f"{self.python_full_version}.{self.build_number}",
            "WINPYFLAVOR": self.flavor,
            "WINPYARCH": self.distribution.architecture if self.distribution else 64,
        }
        env_path = self.winpython_directory / "scripts" / "env.ini"
        env_path.parent.mkdir(parents=True, exist_ok=True)
        self._print_action(f"Creating env.ini environment {env_path}")
        env_path.write_text("\n".join(f"{k}={v}" for k, v in config.items()))

    def build(self):
        """Make or finalise WinPython distribution in the target directory"""
        print(f"Building WinPython with Python archive: {self.python_zip_file.name}")
        self._print_action(f"Creating WinPython {self.winpython_directory} base directory")
        if self.winpython_directory.is_dir() and len(self.winpython_directory.parts)>=4:
            shutil.rmtree(self.winpython_directory)
        # preventive re-Creation of settings directory
        (self.winpython_directory / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True)

        self._extract_python_archive()
        self.distribution = wppm.Distribution(self.winpython_directory / self.python_directory_name, verbose=self.verbose)
        self._copy_essential_files()
        self._create_env_config()

def make_all(build_number: int, release_level: str, basedir_wpy: Path = None,
             source_dirs: Path = None, toolsdirs: str | list[Path] = None,
             verbose: bool = False, flavor: str = "",
             winpyver: str = "", winpyver2: str = ""):
    """
    Make a WinPython distribution for a given set of parameters:
    Args:
        build_number: build number [int]
        release_level: release level (e.g. 'beta1', '') [str]
        basedir_wpy:  top directory of the build (c:\...\Wpy...)
        verbose: Enable verbose output (bool).
        flavor: WinPython flavor (str).
        source_dirs: the python.zip
        toolsdirs: Directory with development tools r'D:\WinPython\basedir34\t.Slim'
    """
    assert basedir_wpy is not None, "The *winpython_dirname* directory must be specified"

    tools_directories = [Path(d) for d in parse_list_argument(toolsdirs, ",")]
    utils.print_box(f"Making WinPython at {basedir_wpy}")
    os.makedirs(basedir_wpy, exist_ok=True)

    builder = WinPythonDistributionBuilder(
        build_number, release_level, Path(basedir_wpy), 
        verbose=verbose, flavor=flavor,
        source_dirs=source_dirs, tools_directories=tools_directories,
        winpyver=winpyver, winpyver2=winpyver2)
    builder.build()

if __name__ == "__main__":
    make_all(
        build_number=1,
        release_level="b3",
        basedir_wpy=r"D:\WinPython\bd314\budot\WPy64-31401b3",
        verbose=True,
        flavor="dot",
        source_dirs=r"D:\WinPython\bd314\packages.win-amd64",
        toolsdirs=r"D:\WinPython\bd314\t.Slim",
        winpyver=r"3.14.0.1dotb3",
        winpyver2=r"3.14.0.1"        
    )
