/**
 * Slice rendering helper functions for 2D slice visualization.
 * This module provides pure functions for slice rendering operations.
 *
 * Related to: 2D slice rendering (axial, coronal, sagittal), mosaic views, crosshairs
 */

import { SLICE_TYPE } from '@/nvdocument'

// WebGL texture unit constants
const TEXTURE0_BACK_VOL = 33984
const TEXTURE2_OVERLAY_VOL = 33986

/**
 * Parameters for updating texture interpolation
 */
export interface UpdateInterpolationParams {
    gl: WebGL2RenderingContext
    layer: number
    isForceLinear?: boolean
    isNearestInterpolation: boolean
    is2DSliceShader: boolean
}

/**
 * Update texture interpolation mode (nearest or linear) for background or overlay layer.
 * @param params - Parameters containing GL context, layer index, and interpolation settings
 */
export function updateInterpolation(params: UpdateInterpolationParams): void {
    const { gl, layer, isForceLinear = false, isNearestInterpolation, is2DSliceShader } = params

    let interp: number = gl.LINEAR
    if (!isForceLinear && isNearestInterpolation) {
        interp = gl.NEAREST
    }

    if (layer === 0) {
        gl.activeTexture(TEXTURE0_BACK_VOL) // background
        // Use 2D texture for background when is2DSliceShader is true
        if (is2DSliceShader) {
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, interp)
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, interp)
        } else {
            gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, interp)
            gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, interp)
        }
    } else {
        gl.activeTexture(TEXTURE2_OVERLAY_VOL) // overlay
        // Overlay is always a 3D texture
        gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, interp)
        gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, interp)
    }
}

/**
 * Parsed mosaic item with rendering information
 */
export interface MosaicItem {
    axCorSag: SLICE_TYPE
    sliceMM: number
    isRender: boolean
    isLabel: boolean
    isCrossLines: boolean
}

/**
 * Result of parsing a mosaic string
 */
export interface MosaicParseResult {
    items: MosaicItem[][]
    axiMM: number[]
    corMM: number[]
    sagMM: number[]
    horizontalOverlap: number
}

/**
 * Parse a mosaic string into structured tile information.
 * @param mosaicStr - The mosaic string specification (e.g., "A -10 0 20; C 0")
 * @returns Parsed mosaic structure with rows of items and slice positions
 */
export function parseMosaicString(mosaicStr: string): MosaicParseResult {
    const normalizedStr = mosaicStr.replaceAll(';', ' ;').trim()
    const tokens = normalizedStr.split(/\s+/)

    const axiMM: number[] = []
    const corMM: number[] = []
    const sagMM: number[] = []
    const rows: MosaicItem[][] = []
    let currentRow: MosaicItem[] = []
    let horizontalOverlap = 0

    let axCorSag = SLICE_TYPE.AXIAL
    let isRender = false
    let isLabel = false
    let isCrossLines = false

    for (let i = 0; i < tokens.length; i++) {
        const token = tokens[i]

        if (token.includes('X')) {
            isCrossLines = true
            continue
        }
        if (token.includes('L')) {
            isLabel = !token.includes('-')
            continue
        }
        if (token.includes('H')) {
            i++
            horizontalOverlap = Math.abs(Math.max(0, Math.min(1, parseFloat(tokens[i]))))
            continue
        }
        if (token.includes('V')) {
            i++
            continue
        }
        if (token.includes('A')) {
            axCorSag = SLICE_TYPE.AXIAL
            continue
        }
        if (token.includes('C')) {
            axCorSag = SLICE_TYPE.CORONAL
            continue
        }
        if (token.includes('S')) {
            axCorSag = SLICE_TYPE.SAGITTAL
            continue
        }
        if (token.includes('R')) {
            isRender = true
            continue
        }
        if (token.includes(';')) {
            if (currentRow.length > 0) {
                rows.push(currentRow)
                currentRow = []
            }
            continue
        }

        const sliceMM = parseFloat(token)
        if (isNaN(sliceMM)) {
            continue
        }

        // Track slice positions for crosshairs
        if (!isRender) {
            if (axCorSag === SLICE_TYPE.AXIAL) {
                axiMM.push(sliceMM)
            }
            if (axCorSag === SLICE_TYPE.CORONAL) {
                corMM.push(sliceMM)
            }
            if (axCorSag === SLICE_TYPE.SAGITTAL) {
                sagMM.push(sliceMM)
            }
        }

        currentRow.push({
            axCorSag,
            sliceMM,
            isRender,
            isLabel,
            isCrossLines
        })

        // Reset per-item flags after creating item
        isRender = false
        isCrossLines = false
    }

    // Don't forget the last row
    if (currentRow.length > 0) {
        rows.push(currentRow)
    }

    return {
        items: rows,
        axiMM,
        corMM,
        sagMM,
        horizontalOverlap
    }
}

/**
 * Parameters for calculating mosaic tile layout
 */
export interface MosaicLayoutParams {
    regionWidth: number
    regionHeight: number
    tileMargin: number
    centerMosaic: boolean
    getFovMM: (axCorSag: SLICE_TYPE, isRender: boolean) => [number, number, number]
    tileGap?: number
}

/**
 * Layout information for a single mosaic tile
 */
export interface MosaicTileLayout {
    left: number
    top: number
    width: number
    height: number
    item: MosaicItem
}

/**
 * Result of mosaic layout calculation
 */
export interface MosaicLayoutResult {
    tiles: MosaicTileLayout[]
    scale: number
    marginLeft: number
    marginTop: number
}

/**
 * Calculate layout positions for mosaic tiles.
 * @param params - Layout parameters
 * @param parsedMosaic - Parsed mosaic structure
 * @returns Layout positions for all tiles
 */
export function calculateMosaicLayout(params: MosaicLayoutParams, parsedMosaic: MosaicParseResult): MosaicLayoutResult {
    const { regionWidth, regionHeight, tileMargin, centerMosaic, getFovMM, tileGap = 0 } = params
    const { items, horizontalOverlap } = parsedMosaic

    // First pass: calculate total dimensions
    let totalHeight = 0
    let maxRowWidth = 0

    const rowLayouts: Array<{ width: number; height: number; tiles: Array<{ w: number; h: number; item: MosaicItem }> }> = []

    for (const row of items) {
        let rowWidth = 0
        let rowHeight = 0
        const rowTiles: Array<{ w: number; h: number; item: MosaicItem }> = []
        let prevWidth = 0

        for (const item of row) {
            const fov = getFovMM(item.axCorSag, item.isRender)
            const w = item.axCorSag === SLICE_TYPE.SAGITTAL ? fov[1] : fov[0]
            const h = item.axCorSag === SLICE_TYPE.AXIAL ? fov[1] : fov[2]

            // Apply horizontal overlap
            if (horizontalOverlap > 0 && !item.isRender && rowTiles.length > 0) {
                rowWidth += Math.round(prevWidth * (1.0 - horizontalOverlap))
            } else if (rowTiles.length > 0) {
                rowWidth += prevWidth + tileGap
            }

            rowTiles.push({ w, h, item })
            rowHeight = Math.max(rowHeight, h)
            prevWidth = w
        }

        // Add the last tile width
        rowWidth += prevWidth

        rowLayouts.push({ width: rowWidth, height: rowHeight, tiles: rowTiles })
        totalHeight += rowHeight
        maxRowWidth = Math.max(maxRowWidth, rowWidth)
    }

    if (maxRowWidth <= 0 || totalHeight <= 0) {
        return { tiles: [], scale: 1, marginLeft: 0, marginTop: 0 }
    }

    // Calculate scale to fit in region
    const scaleW = (regionWidth - 2 * tileMargin - tileGap) / maxRowWidth
    const scaleH = (regionHeight - 2 * tileMargin) / totalHeight
    const scale = Math.min(scaleW, scaleH)

    // Calculate margins
    let marginLeft = tileMargin
    let marginTop = tileMargin
    if (centerMosaic) {
        marginLeft = Math.floor(0.5 * (regionWidth - maxRowWidth * scale))
        marginTop = Math.floor(0.5 * (regionHeight - totalHeight * scale))
    }

    // Second pass: calculate actual positions
    const tiles: MosaicTileLayout[] = []
    let top = 0

    for (const rowLayout of rowLayouts) {
        let left = 0
        let prevWidth = 0

        for (let i = 0; i < rowLayout.tiles.length; i++) {
            const tile = rowLayout.tiles[i]

            // Apply horizontal overlap
            if (horizontalOverlap > 0 && !tile.item.isRender && i > 0) {
                left += Math.round(prevWidth * (1.0 - horizontalOverlap))
            } else if (i > 0) {
                left += prevWidth + tileGap
            }

            tiles.push({
                left,
                top,
                width: tile.w,
                height: tile.h,
                item: tile.item
            })

            prevWidth = tile.w
        }

        top += rowLayout.height
    }

    return { tiles, scale, marginLeft, marginTop }
}

/**
 * Get the lines arrays (horizontal and vertical) for a given slice type.
 * @param axCorSag - Slice orientation
 * @param axiMM - Axial slice positions
 * @param corMM - Coronal slice positions
 * @param sagMM - Sagittal slice positions
 * @returns Object with linesH and linesV arrays
 */
export function getCrossLinesForSliceType(axCorSag: SLICE_TYPE, axiMM: number[], corMM: number[], sagMM: number[]): { linesH: number[]; linesV: number[] } {
    let linesH = corMM.slice()
    let linesV = sagMM.slice()

    if (axCorSag === SLICE_TYPE.CORONAL) {
        linesH = axiMM.slice()
    }
    if (axCorSag === SLICE_TYPE.SAGITTAL) {
        linesH = axiMM.slice()
        linesV = corMM.slice()
    }

    return { linesH, linesV }
}

/**
 * Get the slice dimension index for a given slice type.
 * @param axCorSag - Slice orientation
 * @returns Dimension index (0=i/sagittal, 1=j/coronal, 2=k/axial)
 */
export function getSliceDimension(axCorSag: SLICE_TYPE): number {
    if (axCorSag === SLICE_TYPE.CORONAL) {
        return 1 // j dimension
    }
    if (axCorSag === SLICE_TYPE.SAGITTAL) {
        return 0 // i dimension
    }
    return 2 // k dimension (axial)
}

/**
 * Calculate azimuth and elevation angles for a given slice type and orientation.
 * @param axCorSag - Slice orientation
 * @param isRadiological - Whether to use radiological convention
 * @returns Object with azimuth and elevation in degrees
 */
export function getSliceAngles(axCorSag: SLICE_TYPE, isRadiological: boolean): { azimuth: number; elevation: number } {
    let elevation = 0
    let azimuth = 0

    if (axCorSag === SLICE_TYPE.SAGITTAL) {
        azimuth = isRadiological ? 90 : -90
    } else if (axCorSag === SLICE_TYPE.CORONAL) {
        azimuth = isRadiological ? 180 : 0
    } else {
        // AXIAL
        azimuth = isRadiological ? 180 : 0
        elevation = isRadiological ? -90 : 90
    }

    return { azimuth, elevation }
}

/**
 * Determine if radiological convention should be used based on options and slice type.
 * @param axCorSag - Slice orientation
 * @param isRadiologicalConvention - Global radiological convention setting
 * @param sagittalNoseLeft - Sagittal nose left setting
 * @param customMM - Custom MM value (Infinity or -Infinity for special cases)
 * @returns Whether to use radiological convention for rendering
 */
export function determineRadiologicalConvention(axCorSag: SLICE_TYPE, isRadiologicalConvention: boolean, sagittalNoseLeft: boolean, customMM: number): boolean {
    let isRadiological = isRadiologicalConvention && axCorSag < SLICE_TYPE.SAGITTAL

    if (customMM === Infinity || customMM === -Infinity) {
        isRadiological = customMM !== Infinity
        if (axCorSag === SLICE_TYPE.CORONAL) {
            isRadiological = !isRadiological
        }
    } else if (sagittalNoseLeft && axCorSag === SLICE_TYPE.SAGITTAL) {
        isRadiological = !isRadiological
    }

    return isRadiological
}

/**
 * Calculate width and height to fit a slice within a container, preserving aspect ratio.
 * @param sliceType - Slice orientation
 * @param volScale - Volume scale factors [x, y, z]
 * @param containerWidth - Container width in pixels
 * @param containerHeight - Container height in pixels
 * @returns Tuple of [actualWidth, actualHeight]
 */
export function calculateSliceDimensions(sliceType: SLICE_TYPE, volScale: number[], containerWidth: number, containerHeight: number): [number, number] {
    let xScale: number
    let yScale: number

    switch (sliceType) {
        case SLICE_TYPE.AXIAL:
            xScale = volScale[0]
            yScale = volScale[1]
            break
        case SLICE_TYPE.CORONAL:
            xScale = volScale[0]
            yScale = volScale[2]
            break
        case SLICE_TYPE.SAGITTAL:
            xScale = volScale[1]
            yScale = volScale[2]
            break
        default:
            return [containerWidth, containerHeight]
    }

    // Calculate scale factor to fit within container while preserving aspect ratio
    const aspectRatio = xScale / yScale
    const containerAspect = containerWidth / containerHeight

    let actualWidth: number
    let actualHeight: number

    if (aspectRatio > containerAspect) {
        // width-constrained
        actualWidth = containerWidth
        actualHeight = containerWidth / aspectRatio
    } else {
        // height-constrained
        actualHeight = containerHeight
        actualWidth = containerHeight * aspectRatio
    }

    return [actualWidth, actualHeight]
}
