/**
 * Connectome management functions for handling connectome loading and label operations.
 * This module provides pure functions for connectome data handling.
 */

import type { NVMesh } from '@/nvmesh'
import { MeshType } from '@/nvmesh'
import { NVConnectome, FreeSurferConnectome } from '@/nvconnectome'
import type { Connectome, LegacyConnectome, NVConnectomeNode } from '@/types'
import { NVLabel3D, NVLabel3DStyle, LabelAnchorPoint, LabelLineTerminator } from '@/nvlabel'
import { log } from '@/logger'

/**
 * Parameters for loading a connectome as a mesh
 */
export interface LoadConnectomeAsMeshParams {
    gl: WebGL2RenderingContext
    json: Connectome | LegacyConnectome | FreeSurferConnectome
}

/**
 * Converts various connectome JSON formats to a standardized mesh representation.
 *
 * @param params - Parameters object containing gl context and JSON data
 * @returns The connectome as an NVMesh
 * @throws Error if the JSON is not a known connectome format
 */
export function loadConnectomeAsMesh(params: LoadConnectomeAsMeshParams): NVMesh {
    const { gl, json } = params
    let connectome = json

    if ('data_type' in json && json.data_type === 'fs_pointset') {
        connectome = NVConnectome.convertFreeSurferConnectome(json as FreeSurferConnectome)
        log.warn('converted FreeSurfer connectome', connectome)
    } else if ('nodes' in json) {
        const nodes = json.nodes
        if ('names' in nodes && 'X' in nodes && 'Y' in nodes && 'Z' in nodes && 'Color' in nodes && 'Size' in nodes) {
            // convert dense "legacy" format to sparse format
            connectome = NVConnectome.convertLegacyConnectome(json as LegacyConnectome)
        }
    } else {
        throw new Error('not a known connectome format')
    }

    return new NVConnectome(gl, connectome as LegacyConnectome)
}

/**
 * Label data created from a connectome node
 */
export interface NodeAddedLabelData {
    text: string
    style: NVLabel3DStyle
    position: [number, number, number]
}

/**
 * Creates label data from a connectome node event.
 * The actual label addition should be done by the caller (Niivue).
 *
 * @param node - The connectome node that was added
 * @param lineTerminator - The line terminator enum value
 * @returns Label data for the node
 */
export function createNodeAddedLabelData(node: NVConnectomeNode, lineTerminator: LabelLineTerminator = LabelLineTerminator.NONE): NodeAddedLabelData {
    const rgba = [1, 1, 1, 1]
    return {
        text: node.name,
        style: {
            textColor: rgba,
            bulletScale: 1,
            bulletColor: rgba,
            lineWidth: 0,
            lineColor: rgba,
            lineTerminator,
            textScale: 1.0
        },
        position: [node.x, node.y, node.z]
    }
}

/**
 * Parameters for getting all labels
 */
export interface GetAllLabelsParams {
    meshes: NVMesh[]
    documentLabels: NVLabel3D[]
}

/**
 * Get all 3D labels from document and connectome meshes.
 *
 * @param params - Parameters object containing meshes and document labels
 * @returns Array of all labels
 */
export function getAllLabels(params: GetAllLabelsParams): NVLabel3D[] {
    const { meshes, documentLabels } = params

    const connectomes = meshes.filter((m) => m.type === MeshType.CONNECTOME)
    const meshNodes = connectomes.flatMap((m) => m.nodes as NVConnectomeNode[])
    const meshLabels = meshNodes.map((n) => n.label)

    // filter out undefined labels
    const definedMeshLabels = meshLabels.filter((l): l is NVLabel3D => l !== undefined)
    const labels = [...documentLabels, ...definedMeshLabels]

    return labels
}

/**
 * Parameters for getting connectome labels
 */
export interface GetConnectomeLabelsParams {
    meshes: NVMesh[]
    documentLabels: NVLabel3D[]
}

/**
 * Get all visible connectome and non-anchored mesh labels.
 *
 * @param params - Parameters object containing meshes and document labels
 * @returns Array of visible connectome labels
 */
export function getConnectomeLabels(params: GetConnectomeLabelsParams): NVLabel3D[] {
    const { meshes, documentLabels } = params

    const connectomes = meshes.filter((m) => m.type === MeshType.CONNECTOME && m.showLegend !== false)
    const meshNodes = connectomes.flatMap((m) => m.nodes as NVConnectomeNode[])
    const meshLabels = meshNodes.map((n) => n.label)

    // filter out undefined labels and labels with empty text
    const definedMeshLabels = meshLabels.filter((l): l is NVLabel3D => l !== undefined && l.text !== '')

    // get all of our non-anchored labels
    const nonAnchoredLabels = documentLabels.filter((l) => l.anchor == null || l.anchor === LabelAnchorPoint.NONE)

    // get the unique set of unanchored labels
    const nonAnchoredLabelSet = new Set(definedMeshLabels)
    for (const label of nonAnchoredLabels) {
        nonAnchoredLabelSet.add(label)
    }

    // now add mesh atlases
    const regularMeshes = meshes.filter((m) => m.type === MeshType.MESH)
    for (let i = 0; i < regularMeshes.length; i++) {
        for (let j = 0; j < regularMeshes[i].layers.length; j++) {
            if (regularMeshes[i].layers[j].labels) {
                for (let k = 0; k < regularMeshes[i].layers[j].labels.length; k++) {
                    nonAnchoredLabelSet.add(regularMeshes[i].layers[j].labels[k])
                }
            }
        }
    }

    return Array.from(nonAnchoredLabelSet)
}

/**
 * Convert a FreeSurfer connectome format to the standard connectome format.
 * This is a convenience wrapper around NVConnectome.convertFreeSurferConnectome.
 *
 * @param json - FreeSurfer connectome JSON data
 * @returns Standard connectome format
 */
export function convertFreeSurferConnectome(json: FreeSurferConnectome): Connectome {
    return NVConnectome.convertFreeSurferConnectome(json)
}

/**
 * Convert a legacy connectome format to the standard connectome format.
 * This is a convenience wrapper around NVConnectome.convertLegacyConnectome.
 *
 * @param json - Legacy connectome JSON data
 * @returns Standard connectome format
 */
export function convertLegacyConnectome(json: LegacyConnectome): Connectome {
    return NVConnectome.convertLegacyConnectome(json)
}
