#!/usr/bin/env python3
"""Render deterministic QA views from the actual posed FBX skin points."""

import json
import os
from pathlib import Path

os.environ.setdefault("MPLCONFIGDIR", "/tmp/next11-matplotlib")

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


ROOT = Path(__file__).resolve().parents[1]
DIAGNOSTICS = ROOT / "diagnostics" / "v335"
OUTPUT = DIAGNOSTICS / "V335_DETERMINISTIC_THREE_POINT_GRIP_ACTUAL_FBX_VISUAL_QA.png"

COLORS = {"body": "#bac3ce", "target": "#ff9f43", "hand": "#26c6da"}


def load_case(side):
    path = DIAGNOSTICS / f"V335_{side}_ACTUAL_FBX_GEOMETRY_VISUAL_DATA.json"
    return json.loads(path.read_text(encoding="utf-8"))


def set_equal_2d(ax, x, y, pad=0.04):
    xmin, xmax = float(x.min()), float(x.max())
    ymin, ymax = float(y.min()), float(y.max())
    span = max(xmax - xmin, ymax - ymin) + 2 * pad
    cx, cy = (xmin + xmax) / 2, (ymin + ymax) / 2
    ax.set_xlim(cx - span / 2, cx + span / 2)
    ax.set_ylim(cy - span / 2, cy + span / 2)
    ax.set_aspect("equal", adjustable="box")


def plot_projection(ax, data, x_index, y_index, title):
    points = np.asarray([row[:3] for row in data["points"]], dtype=float)
    groups = np.asarray([row[3] for row in data["points"]])
    for group in ("body", "target", "hand"):
        mask = groups == group
        size = 0.35 if group == "body" else 1.8
        alpha = 0.20 if group == "body" else 0.72
        ax.scatter(points[mask, x_index], points[mask, y_index], s=size, c=COLORS[group], alpha=alpha, linewidths=0, rasterized=True)
    bone_map = {row["name"]: np.asarray(row["p"], dtype=float) for row in data["bones"]}
    for bone in data["bones"]:
        parent = bone.get("parent")
        if parent not in bone_map:
            continue
        p, q = bone_map[parent], bone_map[bone["name"]]
        ax.plot([p[x_index], q[x_index]], [p[y_index], q[y_index]], color="#334155", lw=0.45, alpha=0.50)
    contacts = data.get("contacts") or {}
    for key, color, marker in (
        ("indexAnchor", "#00a878", "o"),
        ("pinkyAnchor", "#00a878", "s"),
    ):
        if key in contacts:
            p = np.asarray(list(contacts[key].values()), dtype=float)
            ax.scatter([p[x_index]], [p[y_index]], s=42, c=color, marker=marker, linewidths=1.4, zorder=8)
    set_equal_2d(ax, points[:, x_index], points[:, y_index])
    ax.set_title(title, fontsize=10, weight="bold")
    ax.grid(color="#d8dee7", alpha=0.35, lw=0.5)
    ax.set_facecolor("#f8fafc")
    ax.tick_params(labelsize=7)


def plot_closeup(ax, data):
    points = np.asarray([row[:3] for row in data["points"]], dtype=float)
    groups = np.asarray([row[3] for row in data["points"]])
    contacts = data.get("contacts") or {}
    contact_points = [np.asarray(list(contacts[key].values()), dtype=float) for key in ("indexAnchor", "pinkyAnchor") if key in contacts]
    center = np.mean(contact_points, axis=0) if contact_points else points.mean(axis=0)
    local = np.linalg.norm(points - center, axis=1) <= 0.16
    for group in ("body", "target", "hand"):
        mask = local & (groups == group)
        size = 0.7 if group == "body" else 3.0
        alpha = 0.12 if group == "body" else 0.78
        ax.scatter(points[mask, 0], points[mask, 2], points[mask, 1], s=size, c=COLORS[group], alpha=alpha, depthshade=False, linewidths=0)
    for key, color, marker in (
        ("indexAnchor", "#00a878", "o"),
        ("pinkyAnchor", "#00a878", "s"),
    ):
        if key in contacts:
            p = np.asarray(list(contacts[key].values()), dtype=float)
            ax.scatter([p[0]], [p[2]], [p[1]], s=58, c=color, marker=marker, linewidths=1.5, depthshade=False)
    radius = 0.085
    ax.set_xlim(center[0] - radius, center[0] + radius)
    ax.set_ylim(center[2] - radius, center[2] + radius)
    ax.set_zlim(center[1] - radius, center[1] + radius)
    ax.view_init(elev=18, azim=-52 if data["side"] == "R" else -128)
    ax.set_box_aspect((1, 1, 1))
    ax.set_title("Actual skin-contact close-up", fontsize=10, weight="bold")
    ax.set_xlabel("X", fontsize=7)
    ax.set_ylabel("Z", fontsize=7)
    ax.set_zlabel("Y", fontsize=7)
    ax.tick_params(labelsize=6)
    ax.grid(alpha=0.25)
    ax.set_facecolor("#f8fafc")


left, right = load_case("L"), load_case("R")
fig = plt.figure(figsize=(16, 9), dpi=160, facecolor="#eef2f7")
grid = fig.add_gridspec(2, 3, left=0.035, right=0.985, top=0.90, bottom=0.065, wspace=0.10, hspace=0.22)

for row, data in enumerate((left, right)):
    side_name = "Left grip" if data["side"] == "L" else "Right grip"
    plot_projection(fig.add_subplot(grid[row, 0]), data, 0, 1, f"{side_name} · front (X/Y)")
    plot_projection(fig.add_subplot(grid[row, 1]), data, 2, 1, f"{side_name} · side (Z/Y)")
    plot_closeup(fig.add_subplot(grid[row, 2], projection="3d"), data)
    metrics = data["metrics"]
    fig.text(
        0.675,
        0.875 - row * 0.445,
        f"{data['side']} · roll {metrics['rollDeg']}° · snap error {metrics['firstSnapErrorM']*1000:.1f} mm · "
        f"palm-line {metrics['lineAngleDeg']:.1f}° · −Z inward {metrics['closeDirectionDot']:.3f} · fingers {'PASS' if metrics['fingerPass'] else 'FAIL'}",
        fontsize=8,
        color="#263445",
    )

fig.suptitle("NEXT11 V335 · Deterministic three-point grip · actual posed FBX skin", fontsize=16, weight="bold", color="#16202d")
fig.text(0.5, 0.925, "Cyan = gripping hand · orange = target lower limb · green = fixed Index/Pinky palm anchors", ha="center", fontsize=9, color="#536273")
fig.text(0.5, 0.018, "Deterministic software projection from the real Nex FBX rig and skinned vertices; no AI-generated imagery and no screen-space proxy markers.", ha="center", fontsize=8, color="#667085")
fig.savefig(OUTPUT, bbox_inches="tight")
print(OUTPUT)
