/**
 * Volume management functions for handling volume arrays and core volume operations.
 * This module provides pure functions for volume array manipulation.
 *
 * Related modules:
 * - VolumeTexture.ts - Texture-related operations
 * - VolumeColormap.ts - Colormap operations
 * - VolumeModulation.ts - Modulation operations
 */

import { NVImage } from '@/nvimage'

/**
 * Result of adding a volume
 */
export interface AddVolumeResult {
    volumes: NVImage[]
    index: number
}

/**
 * Result of removing a volume
 */
export interface RemoveVolumeResult {
    volumes: NVImage[]
    removed: NVImage | null
}

/**
 * Result of setting/reordering a volume
 */
export interface SetVolumeResult {
    volumes: NVImage[]
    back: NVImage | null
    overlays: NVImage[]
}

/**
 * Add a volume to the volumes array
 * @param volumes - Current volumes array
 * @param volume - Volume to add
 * @returns New volumes array and index where volume was added
 */
export function addVolume(volumes: NVImage[], volume: NVImage): AddVolumeResult {
    const newVolumes = [...volumes, volume]
    const index = newVolumes.length === 1 ? 0 : newVolumes.length - 1
    return { volumes: newVolumes, index }
}

/**
 * Get the index of a volume by its unique ID
 * @param volumes - Volumes array to search
 * @param id - Volume ID to find
 * @returns Index of volume, or -1 if not found
 */
export function getVolumeIndexByID(volumes: NVImage[], id: string): number {
    for (let i = 0; i < volumes.length; i++) {
        if (volumes[i].id === id) {
            return i
        }
    }
    return -1
}

/**
 * Get the index of an overlay (non-background volume) by its ID
 * @param volumes - Volumes array to search
 * @param id - Volume ID to find
 * @returns Index in overlays array (volumes[1+]), or -1 if not found
 */
export function getOverlayIndexByID(volumes: NVImage[], id: string): number {
    const overlays = volumes.slice(1)
    for (let i = 0; i < overlays.length; i++) {
        if (overlays[i].id === id) {
            return i
        }
    }
    return -1
}

/**
 * Reorder a volume to a new index position
 * @param volumes - Current volumes array
 * @param volume - Volume to reorder
 * @param toIndex - Target index (0 for background, -1 to remove, or valid index)
 * @returns New volumes array with updated back and overlays
 */
export function setVolume(volumes: NVImage[], volume: NVImage, toIndex = 0): SetVolumeResult {
    const numberOfLoadedImages = volumes.length
    if (toIndex > numberOfLoadedImages) {
        return {
            volumes,
            back: volumes.length > 0 ? volumes[0] : null,
            overlays: volumes.slice(1)
        }
    }

    const volIndex = getVolumeIndexByID(volumes, volume.id)
    const newVolumes = [...volumes]

    if (toIndex === 0) {
        // Move to background
        newVolumes.splice(volIndex, 1)
        newVolumes.unshift(volume)
    } else if (toIndex < 0) {
        // Remove volume
        newVolumes.splice(volIndex, 1)
    } else {
        // Move to specific index
        newVolumes.splice(volIndex, 1)
        newVolumes.splice(toIndex, 0, volume)
    }

    return {
        volumes: newVolumes,
        back: newVolumes.length > 0 ? newVolumes[0] : null,
        overlays: newVolumes.slice(1)
    }
}

/**
 * Remove a volume from the volumes array
 * @param volumes - Current volumes array
 * @param volume - Volume to remove
 * @returns New volumes array and the removed volume
 */
export function removeVolume(volumes: NVImage[], volume: NVImage): RemoveVolumeResult {
    const result = setVolume(volumes, volume, -1)
    return {
        volumes: result.volumes,
        removed: volume
    }
}

/**
 * Remove a volume by its index
 * @param volumes - Current volumes array
 * @param index - Index of volume to remove
 * @returns New volumes array and the removed volume
 */
export function removeVolumeByIndex(volumes: NVImage[], index: number): RemoveVolumeResult {
    if (index >= volumes.length) {
        throw new Error('Index of volume out of bounds')
    }
    return removeVolume(volumes, volumes[index])
}

/**
 * Move a volume to the bottom (background) of the stack
 * @param volumes - Current volumes array
 * @param volume - Volume to move
 * @returns New volumes array with updated order
 */
export function moveVolumeToBottom(volumes: NVImage[], volume: NVImage): SetVolumeResult {
    return setVolume(volumes, volume, 0)
}

/**
 * Move a volume up one position in the stack
 * @param volumes - Current volumes array
 * @param volume - Volume to move
 * @returns New volumes array with updated order
 */
export function moveVolumeUp(volumes: NVImage[], volume: NVImage): SetVolumeResult {
    const volIdx = getVolumeIndexByID(volumes, volume.id)
    return setVolume(volumes, volume, volIdx + 1)
}

/**
 * Move a volume down one position in the stack
 * @param volumes - Current volumes array
 * @param volume - Volume to move
 * @returns New volumes array with updated order
 */
export function moveVolumeDown(volumes: NVImage[], volume: NVImage): SetVolumeResult {
    const volIdx = getVolumeIndexByID(volumes, volume.id)
    return setVolume(volumes, volume, volIdx - 1)
}

/**
 * Move a volume to the top of the stack
 * @param volumes - Current volumes array
 * @param volume - Volume to move
 * @returns New volumes array with updated order
 */
export function moveVolumeToTop(volumes: NVImage[], volume: NVImage): SetVolumeResult {
    return setVolume(volumes, volume, volumes.length - 1)
}

/**
 * Set the opacity of a volume
 * @param volumes - Current volumes array
 * @param volIdx - Index of volume to modify
 * @param newOpacity - New opacity value (0-1)
 * @returns Same volumes array (volume modified in place)
 */
export function setOpacity(volumes: NVImage[], volIdx: number, newOpacity: number): NVImage[] {
    volumes[volIdx].opacity = newOpacity
    return volumes
}

/**
 * Clone a volume
 * @param volumes - Current volumes array
 * @param index - Index of volume to clone
 * @returns Cloned volume
 */
export function cloneVolume(volumes: NVImage[], index: number): NVImage {
    return volumes[index].clone()
}

/**
 * Set the active frame for a 4D volume
 * @param volumes - Current volumes array
 * @param id - Volume ID
 * @param frame4D - Frame number to set (will be clamped to valid range)
 * @returns Same volumes array (volume modified in place)
 */
export function setFrame4D(volumes: NVImage[], id: string, frame4D: number): NVImage[] {
    const idx = getVolumeIndexByID(volumes, id)
    if (idx < 0) {
        return volumes
    }

    const volume = volumes[idx]

    // Clamp frame to valid range
    let clampedFrame = frame4D
    if (clampedFrame > volume.nFrame4D! - 1) {
        clampedFrame = volume.nFrame4D! - 1
    }
    if (clampedFrame < 0) {
        clampedFrame = 0
    }

    // No change needed
    if (clampedFrame === volume.frame4D) {
        return volumes
    }

    volume.frame4D = clampedFrame
    return volumes
}

/**
 * Get the active frame for a 4D volume
 * @param volumes - Current volumes array
 * @param id - Volume ID
 * @returns Current frame number
 */
export function getFrame4D(volumes: NVImage[], id: string): number {
    const idx = getVolumeIndexByID(volumes, id)
    if (idx < 0) {
        return 0
    }
    return volumes[idx].frame4D!
}

/**
 * Generate RGBA data for a volume overlay (for testing/demo purposes)
 * @param volume - Volume to generate data for
 * @returns RGBA data as Uint8ClampedArray
 */
export function overlayRGBA(volume: NVImage): Uint8ClampedArray {
    const hdr = volume.hdr!
    const vox = hdr.dims[1] * hdr.dims[2] * hdr.dims[3]
    const imgRGBA = new Uint8ClampedArray(vox * 4)
    const radius = 0.2 * Math.min(Math.min(hdr.dims[1], hdr.dims[2]), hdr.dims[3])
    const halfX = 0.5 * hdr.dims[1]
    const halfY = 0.5 * hdr.dims[2]
    const halfZ = 0.5 * hdr.dims[3]
    let j = 0
    for (let z = 0; z < hdr.dims[3]; z++) {
        for (let y = 0; y < hdr.dims[2]; y++) {
            for (let x = 0; x < hdr.dims[1]; x++) {
                const dx = Math.abs(x - halfX)
                const dy = Math.abs(y - halfY)
                const dz = Math.abs(z - halfZ)
                const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)
                let v = 0
                if (dist < radius) {
                    v = 255
                }
                imgRGBA[j++] = 0 // Red
                imgRGBA[j++] = v // Green
                imgRGBA[j++] = 0 // Blue
                imgRGBA[j++] = v * 0.5 // Alpha
            }
        }
    }
    return imgRGBA
}
