/**
 * AffineProcessor module
 *
 * Handles affine matrix processing for NIfTI images:
 * - Validates pixel dimensions
 * - Calculates affine matrix from QForm quaternion parameters
 * - Repairs defective affine matrices
 * - Validates scale/intercept values
 *
 * The affine matrix defines the spatial transformation from voxel coordinates
 * to world (scanner) coordinates.
 */

import { NIFTI1, NIFTI2 } from 'nifti-reader-js'
import { log } from '@/logger'
import { isAffineOK } from '@/nvimage/utils'

/**
 * Validate and fix pixel dimensions in NIfTI header.
 * Ensures all spatial pixel dimensions are non-zero.
 *
 * @param hdr - NIfTI header to validate
 */
export function validatePixelDimensions(hdr: NIFTI1 | NIFTI2): void {
    if (hdr.pixDims[1] === 0.0 || hdr.pixDims[2] === 0.0 || hdr.pixDims[3] === 0.0) {
        log.error('pixDims not plausible', hdr)
    }
}

/**
 * Validate and fix scale/intercept values in NIfTI header.
 * Ensures scl_slope is non-zero and scl_inter is defined.
 *
 * @param hdr - NIfTI header to validate
 */
export function validateScaleIntercept(hdr: NIFTI1 | NIFTI2): void {
    // https://github.com/nipreps/fmriprep/issues/2507
    if (isNaN(hdr.scl_slope) || hdr.scl_slope === 0.0) {
        hdr.scl_slope = 1.0
    }
    if (isNaN(hdr.scl_inter)) {
        hdr.scl_inter = 0.0
    }
}

/**
 * Calculate affine matrix from QForm quaternion parameters.
 * The QForm method uses quaternion rotation parameters to define the spatial transform.
 * This is used when useQFormNotSForm is true, or when the affine is invalid,
 * or when qform_code > sform_code.
 *
 * @param hdr - NIfTI header containing quaternion parameters
 * @param useQFormNotSForm - Force use of QForm even if SForm is valid
 */
export function calculateAffineFromQForm(hdr: NIFTI1 | NIFTI2, useQFormNotSForm: boolean): void {
    const affineOK = isAffineOK(hdr.affine)

    if (!useQFormNotSForm && affineOK && hdr.qform_code <= hdr.sform_code) {
        // SForm is valid and preferred, no need to calculate from QForm
        return
    }

    log.debug('spatial transform based on QForm')

    // https://github.com/rii-mango/NIFTI-Reader-JS/blob/6908287bf99eb3bc4795c1591d3e80129da1e2f6/src/nifti1.js#L238
    // Define a, b, c, d for coding convenience
    const b = hdr.quatern_b
    const c = hdr.quatern_c
    const d = hdr.quatern_d

    // quatern_a is a parameter in quaternion [a, b, c, d], which is required in affine calculation (METHOD 2)
    // mentioned in the nifti1.h file
    // It can be calculated by a = sqrt(1.0-(b*b+c*c+d*d))
    const a = Math.sqrt(1.0 - (Math.pow(b, 2) + Math.pow(c, 2) + Math.pow(d, 2)))
    const qfac = hdr.pixDims[0] === 0 ? 1 : hdr.pixDims[0]

    const quatern_R = [
        [a * a + b * b - c * c - d * d, 2 * b * c - 2 * a * d, 2 * b * d + 2 * a * c],
        [2 * b * c + 2 * a * d, a * a + c * c - b * b - d * d, 2 * c * d - 2 * a * b],
        [2 * b * d - 2 * a * c, 2 * c * d + 2 * a * b, a * a + d * d - c * c - b * b]
    ]

    const affine = hdr.affine
    for (let ctrOut = 0; ctrOut < 3; ctrOut += 1) {
        for (let ctrIn = 0; ctrIn < 3; ctrIn += 1) {
            affine[ctrOut][ctrIn] = quatern_R[ctrOut][ctrIn] * hdr.pixDims[ctrIn + 1]
            if (ctrIn === 2) {
                affine[ctrOut][ctrIn] *= qfac
            }
        }
    }

    // The last row of affine matrix is the offset vector
    affine[0][3] = hdr.qoffset_x
    affine[1][3] = hdr.qoffset_y
    affine[2][3] = hdr.qoffset_z

    hdr.affine = affine
}

/**
 * Repair defective affine matrix by creating a simple diagonal matrix
 * from pixel dimensions. This is a fallback when both QForm and SForm
 * produce invalid affine matrices.
 *
 * @param hdr - NIfTI header with defective affine matrix
 */
export function repairDefectiveAffine(hdr: NIFTI1 | NIFTI2): void {
    if (isAffineOK(hdr.affine)) {
        // Affine is already valid, no repair needed
        return
    }

    log.debug('Defective NIfTI: spatial transform does not make sense')

    let x = hdr.pixDims[1]
    let y = hdr.pixDims[2]
    let z = hdr.pixDims[3]

    if (isNaN(x) || x === 0.0) {
        x = 1.0
    }
    if (isNaN(y) || y === 0.0) {
        y = 1.0
    }
    if (isNaN(z) || z === 0.0) {
        z = 1.0
    }

    hdr.pixDims[1] = x
    hdr.pixDims[2] = y
    hdr.pixDims[3] = z

    const affine = [
        [x, 0, 0, 0],
        [0, y, 0, 0],
        [0, 0, z, 0],
        [0, 0, 0, 1]
    ]

    hdr.affine = affine
}

/**
 * Process NIfTI affine matrix: validate, calculate from QForm if needed, and repair if defective.
 * This is the main entry point that coordinates all affine processing steps.
 *
 * @param hdr - NIfTI header to process
 * @param useQFormNotSForm - Prefer QForm over SForm for spatial transform
 */
export function processAffine(hdr: NIFTI1 | NIFTI2, useQFormNotSForm: boolean): void {
    // Step 1: Validate pixel dimensions
    validatePixelDimensions(hdr)

    // Step 2: Validate scale/intercept
    validateScaleIntercept(hdr)

    // Step 3: Calculate affine from QForm if needed
    calculateAffineFromQForm(hdr, useQFormNotSForm)

    // Step 4: Repair affine if still defective
    repairDefectiveAffine(hdr)
}
