#!/usr/bin/env python3
"""Original dimension fixture, not a Procedura execution or model-quality test.

Run: python scale-fixture.py > scale-results.json
Python standard library only; no network, model, engine, or third-party imports.
The center/scale equation is independently implemented from the cited source.
The OBJ round trip covers vertex text only, not a complete mesh importer.
SPDX-License-Identifier: MIT
Copyright (c) 2026 colinkoko
"""
import hashlib
import itertools
import json
import math

REVISION = "fac191ed49f55fcc2e0f23897e986042249f59fe"
SOURCE = f"https://github.com/SpatiaOS/Procedura/blob/{REVISION}/src/mesh/normalize.ts"


def bbox(vertices):
    if not vertices or any(len(v) != 3 or not all(math.isfinite(x) for x in v) for v in vertices):
        raise ValueError("Fixture needs finite 3D vertices")
    lo = [min(v[a] for v in vertices) for a in range(3)]
    hi = [max(v[a] for v in vertices) for a in range(3)]
    return lo, hi, [hi[a] - lo[a] for a in range(3)]


def normalize(vertices):
    lo, hi, size = bbox(vertices)
    longest = max(size)
    if longest <= 0:
        raise ValueError("Fixture has zero extent")
    center = [(a + b) / 2 for a, b in zip(lo, hi)]
    scale = 2 / longest
    return [[(v[a] - center[a]) * scale for a in range(3)] for v in vertices], center, scale


def vertices_for_box(size, origin):
    return [[origin[a] + bits[a] * size[a] for a in range(3)]
            for bits in itertools.product((0, 1), repeat=3)]


def close(actual, expected):
    assert len(actual) == len(expected)
    assert all(math.isclose(a, b, rel_tol=1e-10, abs_tol=1e-10)
               for a, b in zip(actual, expected)), (actual, expected)


CASES = [
    ("base", [100, 50, 25], [0, 0, 0], [2, 1, .5]),
    ("length_only_double", [200, 50, 25], [0, 0, 0], [2, .5, .25]),
    ("all_dimensions_double", [200, 100, 50], [0, 0, 0], [2, 1, .5]),
    ("translated_base", [100, 50, 25], [30, -10, 7], [2, 1, .5]),
    ("different_longest_axis", [40, 160, 80], [0, 0, 0], [.5, 2, 1]),
]


def run():
    records = []
    for name, size, origin, expected in CASES:
        vertices = vertices_for_box(size, origin)
        normalized, center, factor = normalize(vertices)
        # The fixture authors choose millimeters; this is not an upstream unit guarantee.
        meters_per_source_unit = .001
        restore_factor = max(size) / 2 * meters_per_source_unit
        obj = "".join("v " + " ".join(format(x, ".12g") for x in v) + "\n" for v in normalized)
        parsed = [[float(x) for x in line.split()[1:]] for line in obj.splitlines()]
        lo, hi, measured = bbox(parsed)
        close(measured, expected)
        close([(a + b) / 2 for a, b in zip(lo, hi)], [0, 0, 0])
        restored = [[x * restore_factor for x in v] for v in parsed]
        close(bbox(restored)[2], [x * meters_per_source_unit for x in size])
        for old, new in zip(vertices, parsed):
            # Invert both scale and centering, then convert our declared source units.
            close([new[a] * restore_factor + center[a] * meters_per_source_unit for a in range(3)],
                  [x * meters_per_source_unit for x in old])
        records.append({
            "name": name, "source_bbox_size": size, "source_bbox_min": origin,
            "fixture_source_unit": "millimeter (author-defined)",
            "vertex_count": len(vertices), "bbox_center": center,
            "normalization_factor": factor,
            "normalized_obj_vertex_text_sha256": hashlib.sha256(obj.encode()).hexdigest(),
            "normalized_bbox_size": measured,
            "meters_per_normalized_unit_to_restore_dimensions": restore_factor,
            "restored_bbox_meters": bbox(restored)[2],
            "naive_shared_scale_0_05_bbox_meters": [x * .05 for x in measured],
            "source_position_restored": True,
        })
    rejected = 0
    for bad in ([], [[0, 0, 0]], [[float("nan"), 1, 2]]):
        try:
            normalize(bad)
        except ValueError:
            rejected += 1
    assert rejected == 3
    close(records[0]["normalized_bbox_size"], records[2]["normalized_bbox_size"])
    close(records[0]["normalized_bbox_size"], records[3]["normalized_bbox_size"])
    return {
        "schema_version": 1,
        "provenance": {
            "kind": "original synthetic arithmetic and vertex-text fixture",
            "inspected_source_url": SOURCE,
            "inspected_revision": REVISION,
            "equation": "p_normalized = (p - bbox_center) * 2 / longest_bbox_side",
            "target_half_extent": 1,
            "scope": "normal successful publishMesh OBJ path; opt-in STL copies transformed geometry",
            "not_tested": ["Procedura execution", "OpenSCAD compilation", "real generated outputs", "mesh faces", "materials", "USD/URDF motion export", "game-engine or DCC import", "runtime performance"],
        },
        "checks": {"box_cases_passed": len(records), "invalid_fixture_inputs_rejected": rejected,
                   "dimension_restoration_passed": True, "position_restoration_passed": True,
                   "normalization_collapses_uniform_size_variants": True},
        "cases": records,
    }


if __name__ == "__main__":
    print(json.dumps(run(), indent=2, allow_nan=False))
