//! Tangent generation utility — per-triangle accumulation + Gram-Schmidt orthogonalization.
//! Generates tangent vectors (with bitangent sign in .w) for meshes without tangent data.

/// Generate tangent vectors from triangle mesh data.
/// Returns tangents with .w = bitangent handedness sign (1.0 or -1.0).
pub fn generate_tangents(
    positions: &[[f32; 3]],
    normals: &[[f32; 3]],
    uvs: &[[f32; 2]],
    indices: &[u32],
) -> Vec<[f32; 4]> {
    let vertex_count = positions.len();
    let mut tangents = vec![[0.0f32; 3]; vertex_count];
    let mut bitangents = vec![[0.0f32; 3]; vertex_count];

    // Accumulate per-triangle tangent/bitangent
    for tri in indices.chunks_exact(3) {
        let i0 = tri[0] as usize;
        let i1 = tri[1] as usize;
        let i2 = tri[2] as usize;

        let p0 = positions[i0];
        let p1 = positions[i1];
        let p2 = positions[i2];

        let uv0 = uvs[i0];
        let uv1 = uvs[i1];
        let uv2 = uvs[i2];

        let edge1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
        let edge2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
        let duv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
        let duv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];

        let r = 1.0 / (duv1[0] * duv2[1] - duv1[1] * duv2[0] + 1e-10);

        let t = [
            (edge1[0] * duv2[1] - edge2[0] * duv1[1]) * r,
            (edge1[1] * duv2[1] - edge2[1] * duv1[1]) * r,
            (edge1[2] * duv2[1] - edge2[2] * duv1[1]) * r,
        ];
        let b = [
            (edge2[0] * duv1[0] - edge1[0] * duv2[0]) * r,
            (edge2[1] * duv1[0] - edge1[1] * duv2[0]) * r,
            (edge2[2] * duv1[0] - edge1[2] * duv2[0]) * r,
        ];

        for &idx in &[i0, i1, i2] {
            for j in 0..3 {
                tangents[idx][j] += t[j];
                bitangents[idx][j] += b[j];
            }
        }
    }

    // Gram-Schmidt orthogonalize + compute handedness
    let mut result = vec![[0.0f32; 4]; vertex_count];
    for i in 0..vertex_count {
        let n = normals[i];
        let t = tangents[i];

        // t' = normalize(t - n * dot(n, t))
        let n_dot_t = n[0] * t[0] + n[1] * t[1] + n[2] * t[2];
        let ortho = [t[0] - n[0] * n_dot_t, t[1] - n[1] * n_dot_t, t[2] - n[2] * n_dot_t];
        let len = (ortho[0] * ortho[0] + ortho[1] * ortho[1] + ortho[2] * ortho[2]).sqrt();

        if len > 1e-6 {
            let inv_len = 1.0 / len;
            let normalized = [ortho[0] * inv_len, ortho[1] * inv_len, ortho[2] * inv_len];

            // Handedness: sign of dot(cross(n, t), b)
            let cross = [
                n[1] * normalized[2] - n[2] * normalized[1],
                n[2] * normalized[0] - n[0] * normalized[2],
                n[0] * normalized[1] - n[1] * normalized[0],
            ];
            let b = bitangents[i];
            let w = if cross[0] * b[0] + cross[1] * b[1] + cross[2] * b[2] < 0.0 {
                -1.0
            } else {
                1.0
            };

            result[i] = [normalized[0], normalized[1], normalized[2], w];
        } else {
            result[i] = [1.0, 0.0, 0.0, 1.0]; // fallback tangent
        }
    }

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn plane_tangent_generation() {
        let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 1.0], [0.0, 0.0, 1.0]];
        let normals = [[0.0, 1.0, 0.0]; 4];
        let uvs = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
        let indices = [0, 1, 2, 0, 2, 3];
        let tangents = generate_tangents(&positions, &normals, &uvs, &indices);
        assert_eq!(tangents.len(), 4);
        // Tangent should roughly point along X axis
        for t in &tangents {
            assert!(t[0].abs() > 0.5, "tangent X should be significant: {:?}", t);
            assert!((t[3].abs() - 1.0).abs() < 0.001, "handedness should be +-1");
        }
    }

    #[test]
    fn empty_mesh_returns_empty() {
        let tangents = generate_tangents(&[], &[], &[], &[]);
        assert!(tangents.is_empty());
    }

    #[test]
    fn degenerate_uv_fallback() {
        // Triangle with zero-area UVs should produce fallback tangent
        let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
        let normals = [[0.0, 0.0, 1.0]; 3];
        let uvs = [[0.0, 0.0]; 3]; // degenerate
        let indices = [0, 1, 2];
        let tangents = generate_tangents(&positions, &normals, &uvs, &indices);
        assert_eq!(tangents.len(), 3);
    }
}
