"""Analýza sklonů stěn vůči dělící rovině a detekce podkosů.

Konvence
--------
Dělící rovina = z = 0 (rovina XY).  Směr vyjmutí odlitku (pull direction)
d = +Z.

Úkos (draft angle) je definován jako odklon stěny od směru vyjmutí:

    delta = 90° - angle(n, +Z) = asin(n · ẑ)

    * svislá stěna            ->  delta =   0°   (nulový úkos)
    * stěna 87° od vodorovné  ->  delta =   3°
    * vodorovná horní plocha  ->  delta =  90°
    * převis / podkos         ->  delta <   0°

Požadavek na odformovatelnost: delta >= delta_req pro každou stěnu
(kromě ploch ležících v dělící rovině, které tvoří dno modelu).
"""
from __future__ import annotations

import numpy as np

from .stlio import face_normals


def triangle_areas(vertices: np.ndarray, faces: np.ndarray) -> np.ndarray:
    v0 = vertices[faces[:, 0]]
    v1 = vertices[faces[:, 1]]
    v2 = vertices[faces[:, 2]]
    return 0.5 * np.linalg.norm(np.cross(v1 - v0, v2 - v0), axis=1)


def draft_angles(vertices: np.ndarray, faces: np.ndarray) -> np.ndarray:
    """Úkos každého trojúhelníku ve stupních (viz konvence v docstringu)."""
    n = face_normals(vertices, faces)
    return np.degrees(np.arcsin(np.clip(n[:, 2], -1.0, 1.0)))


def base_face_mask(vertices: np.ndarray, faces: np.ndarray,
                   z_tol: float = 1e-6) -> np.ndarray:
    """True pro trojúhelníky ležící celé v dělící rovině (dno modelu)."""
    z = vertices[faces][:, :, 2]
    return np.all(np.abs(z) <= z_tol, axis=1)


def analyze(vertices: np.ndarray, faces: np.ndarray,
            required_draft_deg: float = 3.0, z_tol: float = 1e-6) -> dict:
    """Lokální analýza úkosů původní sítě.

    Vrací slovník se statistikami.  Pozor: lokální test podle normály je
    *nutnou*, nikoli postačující podmínkou odformovatelnosti — globální
    podkosy (zastínění jinou částí modelu) odhalí až rasterizace
    (viz raster.height_field, klíč 'multilayer').
    """
    delta = draft_angles(vertices, faces)
    area = triangle_areas(vertices, faces)
    base = base_face_mask(vertices, faces, z_tol)

    live = ~base
    a_live = area[live]
    d_live = delta[live]
    tot = float(a_live.sum()) if a_live.size else 0.0

    undercut = d_live < 0.0
    insufficient = (d_live >= 0.0) & (d_live < required_draft_deg)

    def frac(mask):
        return float(area[live][mask].sum() / tot * 100.0) if tot > 0 else 0.0

    hist_edges = np.array([-90, -45, -20, -5, -0.001, 0.001, 1, 3, 5, 10,
                           20, 45, 90.001])
    hist, _ = np.histogram(d_live, bins=hist_edges, weights=a_live)

    return {
        "required_draft_deg": float(required_draft_deg),
        "n_faces": int(faces.shape[0]),
        "n_base_faces": int(base.sum()),
        "total_area_mm2": tot,
        "min_draft_deg": float(d_live.min()) if d_live.size else 0.0,
        "median_draft_deg": float(np.median(d_live)) if d_live.size else 0.0,
        "n_undercut_faces": int(undercut.sum()),
        "n_insufficient_faces": int(insufficient.sum()),
        "area_undercut_pct": frac(undercut),
        "area_insufficient_pct": frac(insufficient),
        "area_ok_pct": frac(d_live >= required_draft_deg),
        "hist_edges": hist_edges.tolist(),
        "hist_area_mm2": hist.tolist(),
        "per_face_draft_deg": delta,
        "per_face_area_mm2": area,
        "base_face_mask": base,
    }


def min_draft_on_model(vertices: np.ndarray, faces: np.ndarray,
                       z_tol: float = 1e-9) -> float:
    """Nejhorší úkos skutečné sítě korigovaného modelu (bez dna)."""
    d = draft_angles(vertices, faces)
    live = ~base_face_mask(vertices, faces, z_tol)
    return float(d[live].min()) if live.any() else 90.0


def min_draft_on_mold(vertices: np.ndarray, faces: np.ndarray) -> float:
    """Nejhorší úkos dutiny formy (stěny formy mířící dolů = povrch odlitku).

    Svislé vnější stěny formy (n_z = 0) se nezapočítávají — netvoří dutinu.
    """
    n = face_normals(vertices, faces)
    cav = n[:, 2] < -1e-9
    if not np.any(cav):
        return 90.0
    return float(np.degrees(np.arcsin(np.clip(-n[cav, 2], -1.0, 1.0))).min())


def bbox(vertices: np.ndarray):
    return vertices.min(axis=0), vertices.max(axis=0)
