#!/usr/bin/env python3
# Copyright (C) 2023-2026 Luis Henrique Cassis Fagundes
# SPDX-License-Identifier: Apache-2.0

"""Build, validate and smoke wheel/sdist outside the checkout; upload nothing."""

import ast
from pathlib import Path
import subprocess
import sys
import tempfile


ROOT = Path(__file__).resolve().parents[1]


def run(*args, cwd=ROOT, env=None):
    subprocess.run([sys.executable, *map(str, args)], cwd=cwd, env=env, check=True)


def main():
    # Read the declared API without importing either an editable installation or
    # the source package into the distribution smoke's interpreter.
    tree = ast.parse((ROOT / "machinome_mechanics" / "__init__.py").read_text())
    expected_exports = next(
        ast.literal_eval(node.value) for node in tree.body
        if isinstance(node, ast.Assign)
        and any(isinstance(target, ast.Name) and target.id == "__all__"
                for target in node.targets)
    )
    # Retain this interpreter's dependencies (including a development machinome),
    # but never let the source mechanics package satisfy an installed smoke test.
    dependency_paths = [
        str(Path(path).resolve()) for path in sys.path if path
        and not Path(path).resolve().is_relative_to(ROOT)
    ]
    with tempfile.TemporaryDirectory(prefix="machinome-mechanics-dist-") as directory:
        scratch = Path(directory)
        dist = scratch / "dist"
        run("-m", "build", "--outdir", dist)
        archives = sorted(dist.iterdir())
        assert len(archives) == 2, archives
        run("-m", "twine", "check", "--strict", *archives)
        for index, archive in enumerate(archives):
            installed = scratch / str(index)
            run("-m", "pip", "install", "--no-deps", "--target", installed,
                archive, cwd=scratch)
            probe = """
import importlib.metadata as metadata
from pathlib import Path
from solid2 import get_animation_time
from solid2.core.object_base import OpenSCADConstant
import machinome_mechanics as mechanics
installed = Path(INSTALLED).resolve()
assert Path(mechanics.__file__).resolve().is_relative_to(installed)
assert mechanics.__all__ == EXPECTED_EXPORTS
assert all(callable(getattr(mechanics, name)) for name in EXPECTED_EXPORTS)
from math import isclose, pi
assert isclose(mechanics.rolling_travel(360, 2), 4 * pi)
assert mechanics.cycloidal_ratio(20, 21) == -0.05
assert mechanics.indexed_advance(810, 7.5, lambda p: 7.5*p/360) == 16.875
assert isinstance(mechanics.indexed_advance(get_animation_time(), 7.5,
                  lambda p: 7.5*p/360), OpenSCADConstant)
assert isclose(mechanics.internal_mesh_angle(0, 126, 54, 45), -60)
assert isinstance(mechanics.internal_mesh_angle(0, 126, 54, get_animation_time()),
                  OpenSCADConstant)
assert isclose(mechanics.harmonic_cam_lift(280, 18, 240, 80), 9)
assert isinstance(mechanics.harmonic_cam_lift(get_animation_time(), 18),
                  OpenSCADConstant)
assert isinstance(mechanics.cycloidal_ratio(20 + get_animation_time(), 21),
                  OpenSCADConstant)
assert isinstance(mechanics.rolling_travel(get_animation_time(), 2),
                  OpenSCADConstant)
assert isclose(mechanics.rolling_angle(4 * pi, 2), 360)
assert isclose(mechanics.pulley_pitch_radius(117, 2) * 2 * pi, 234)
assert isinstance(mechanics.pulley_pitch_radius(20, get_animation_time()),
                  OpenSCADConstant)
assert mechanics.belt_tangent_points((0, 0), 2, (10, 0), 2) == ((0, 2), (10, 2))
assert isinstance(mechanics.belt_tangent_points(
    (get_animation_time(), 0), 2, (10, 0), 3)[0][0], OpenSCADConstant)
assert isclose(mechanics.belt_path_metrics(((0, 0), (10, 0)), (2, 2))['length'],
               20 + 4 * pi)
assert isinstance(mechanics.belt_path_metrics(
    ((get_animation_time(), 0), (10, 0)), (2, 3))['length'], OpenSCADConstant)
assert isclose(mechanics.belt_pulley_angle(10 + pi, 2, 90, 10, -1), 180)
assert isinstance(mechanics.belt_pulley_angle(get_animation_time(), 2, 90),
                  OpenSCADConstant)
assert isinstance(mechanics.rolling_angle(get_animation_time(), 2),
                  OpenSCADConstant)
assert mechanics.screw_travel(360, 2) == 2
assert mechanics.piston_height(0, 15, 60) == 75
assert mechanics.circle_intersection((0, 0), 5, (8, 0), 5) == (4, 3)
assert mechanics.two_link_angles(8, 0, 5, 3) == (0, 0)
assert isclose(mechanics.four_bar_pose(90, (0,0), (4,0), 3, 4, 3, -1)
               ['rocker_angle'], 90)
assert isinstance(mechanics.four_bar_pose(get_animation_time(), (0,0), (6,0),
                                          2, 6, 4)['coupler_angle'], OpenSCADConstant)
assert all(isinstance(angle, OpenSCADConstant) for angle in
           mechanics.two_link_angles(3 + get_animation_time(), 4, 5, 5))
assert isinstance(mechanics.piston_height(get_animation_time(), 15, 60),
                  OpenSCADConstant)
package = metadata.distribution('machinome-mechanics')
assert package.metadata['License-Expression'] == 'Apache-2.0'
assert 'machinome>=0.7.0' in package.requires
files = [str(path) for path in package.files]
assert any(path.endswith('/licenses/LICENSE') for path in files)
assert any(path.endswith('/licenses/NOTICE') for path in files)
assert not any(path.startswith('machinome/') for path in files)
print('Installed numeric/symbolic smoke passed:', mechanics.__file__)
"""
            prefix = (
                "import sys\nsys.path[:0] = "
                + repr([str(installed), *dependency_paths])
                + "\nINSTALLED = " + repr(str(installed)) + "\n"
                + "EXPECTED_EXPORTS = " + repr(expected_exports) + "\n"
            )
            run("-I", "-c", prefix + probe, cwd=scratch)
    print("Wheel and sdist validated; nothing uploaded.")


if __name__ == "__main__":
    main()
