pub const MATRIX_ROWS: u32 = 12;
pub const MATRIX_COLS: u32 = 7;

const BASE_MATRIX_SPAN: f32 = 24.0;
const MATRIX_SPAN_GROWTH: f32 = 0.6;
const MIN_MATRIX_SPAN: f32 = 24.0;
const MAX_MATRIX_SPAN: f32 = 58.0;
const MIN_CELL_SPACING: f32 = 0.52;
const MAX_CELL_SPACING: f32 = 3.8;
const MIN_BOARD_PADDING: f32 = 2.6;
const MAX_BOARD_PADDING: f32 = 6.8;
const MATRIX_OFFSET_Y: f32 = -2.4;
const MATRIX_OFFSET_Z: f32 = 12.0;
const CAMERA_FOV: f32 = 36.0;
const CAMERA_DISTANCE_MIN: f32 = 30.0;
const CAMERA_DISTANCE_MAX: f32 = 122.0;
const CAMERA_FIT_PADDING: f32 = 1.04;
const CAMERA_ELEVATION_DEG: f32 = 64.0;
const CAMERA_TARGET_X: f32 = 0.0;
const CAMERA_TARGET_Y: f32 = MATRIX_OFFSET_Y + 0.2;
const CAMERA_TARGET_Z: f32 = MATRIX_OFFSET_Z - 0.4;

pub struct MatrixLayout {
    pub cell_spacing: f32,
    pub board_width: f32,
    pub board_depth: f32,
    pub board_padding: f32,
    pub label_float_offset: f32,
}

impl MatrixLayout {
    pub fn new(rows: u32, cols: u32) -> Self {
        let longest_edge = rows.max(cols).max(1) as f32;
        let edge_span = (longest_edge - 1.0).max(1.0);
        let target_span = (BASE_MATRIX_SPAN + edge_span * MATRIX_SPAN_GROWTH)
            .clamp(MIN_MATRIX_SPAN, MAX_MATRIX_SPAN);
        let cell_spacing = (target_span / edge_span).clamp(MIN_CELL_SPACING, MAX_CELL_SPACING);
        let board_width = cols.max(1) as f32 * cell_spacing;
        let board_depth = rows.max(1) as f32 * cell_spacing;
        let board_padding = (cell_spacing * 1.62).clamp(MIN_BOARD_PADDING, MAX_BOARD_PADDING);
        let label_float_offset = (cell_spacing * 0.42).clamp(0.36, 1.12);

        Self {
            cell_spacing,
            board_width,
            board_depth,
            board_padding,
            label_float_offset,
        }
    }
}

pub fn build_view_projection(aspect: f32, layout: &MatrixLayout) -> [[f32; 4]; 4] {
    let camera_distance = fit_camera_distance(
        layout.board_width,
        layout.board_depth,
        layout.board_padding,
        aspect,
    );
    let elevation = CAMERA_ELEVATION_DEG.to_radians();
    let target = glam::Vec3::new(CAMERA_TARGET_X, CAMERA_TARGET_Y, CAMERA_TARGET_Z);
    let eye = glam::Vec3::new(
        CAMERA_TARGET_X,
        CAMERA_TARGET_Y + elevation.sin() * camera_distance,
        CAMERA_TARGET_Z + elevation.cos() * camera_distance,
    );
    let view = glam::Mat4::look_at_rh(eye, target, glam::Vec3::Y);
    let projection = glam::Mat4::perspective_rh(CAMERA_FOV.to_radians(), aspect, 0.1, 500.0);
    let open_gl_to_wgpu = glam::Mat4::from_cols_array(&[
        1.0, 0.0, 0.0, 0.0, //
        0.0, 1.0, 0.0, 0.0, //
        0.0, 0.0, 0.5, 0.0, //
        0.0, 0.0, 0.5, 1.0,
    ]);

    (open_gl_to_wgpu * projection * view).to_cols_array_2d()
}

pub fn glyph_world_position(
    row: u32,
    col: u32,
    rows: u32,
    cols: u32,
    layout: &MatrixLayout,
    pressure: f32,
) -> ([f32; 4], f32) {
    let normalized = pressure.clamp(0.0, 1.0);
    let x = (col as f32 - cols as f32 / 2.0 + 0.5) * layout.cell_spacing;
    let z = (row as f32 - rows as f32 / 2.0 + 0.5) * layout.cell_spacing;

    (
        [
            x,
            MATRIX_OFFSET_Y + layout.label_float_offset,
            MATRIX_OFFSET_Z + z,
            1.0,
        ],
        normalized,
    )
}

fn fit_camera_distance(board_width: f32, board_depth: f32, board_padding: f32, aspect: f32) -> f32 {
    let padded_width = board_width + board_padding * 2.0;
    let padded_depth = board_depth + board_padding * 2.0;
    let safe_aspect = aspect.max(0.5);
    let effective_half_span = (padded_depth * 0.5).max((padded_width * 0.5) / safe_aspect);
    let fov_radians = (CAMERA_FOV * 0.5).to_radians();
    let fit_distance = (effective_half_span / fov_radians.tan()) * CAMERA_FIT_PADDING;
    fit_distance.clamp(CAMERA_DISTANCE_MIN, CAMERA_DISTANCE_MAX)
}
