/*!
This module provides functionality for polyfilling `f16` input/output variables
when the `StorageInputOutput16` capability is not available or disabled.

It works by:

1. Declaring `f16` I/O variables as `f32` in SPIR-V
2. Converting between `f16` and `f32` at runtime using `OpFConvert`
3. Maintaining mappings to track which variables need conversion
*/

use crate::back::spv::{Instruction, LocalType, NumericType, Word};
use alloc::vec::Vec;

/// Manages `f16` I/O polyfill state and operations.
#[derive(Default)]
pub(in crate::back::spv) struct F16IoPolyfill {
    use_native: bool,
    io_var_to_f32_type: crate::FastHashMap<Word, Word>,
}

impl F16IoPolyfill {
    pub fn new(use_storage_input_output_16: bool) -> Self {
        Self {
            use_native: use_storage_input_output_16,
            io_var_to_f32_type: crate::FastHashMap::default(),
        }
    }

    pub fn needs_polyfill(&self, ty_inner: &crate::TypeInner) -> bool {
        use crate::{ScalarKind as Sk, TypeInner};

        !self.use_native
            && match *ty_inner {
                TypeInner::Scalar(ref s) if s.kind == Sk::Float && s.width == 2 => true,
                TypeInner::Vector { scalar, .. }
                    if scalar.kind == Sk::Float && scalar.width == 2 =>
                {
                    true
                }
                _ => false,
            }
    }

    pub fn register_io_var(&mut self, variable_id: Word, f32_type_id: Word) {
        self.io_var_to_f32_type.insert(variable_id, f32_type_id);
    }

    pub fn get_f32_io_type(&self, variable_id: Word) -> Option<Word> {
        self.io_var_to_f32_type.get(&variable_id).copied()
    }

    pub fn emit_f16_to_f32_conversion(
        f16_value_id: Word,
        f32_type_id: Word,
        converted_id: Word,
        body: &mut Vec<Instruction>,
    ) {
        body.push(Instruction::unary(
            spirv::Op::FConvert,
            f32_type_id,
            converted_id,
            f16_value_id,
        ));
    }

    pub fn emit_f32_to_f16_conversion(
        f32_value_id: Word,
        f16_type_id: Word,
        converted_id: Word,
        body: &mut Vec<Instruction>,
    ) {
        body.push(Instruction::unary(
            spirv::Op::FConvert,
            f16_type_id,
            converted_id,
            f32_value_id,
        ));
    }

    pub fn create_polyfill_type(ty_inner: &crate::TypeInner) -> Option<LocalType> {
        use crate::{ScalarKind as Sk, TypeInner};

        match *ty_inner {
            TypeInner::Scalar(ref s) if s.kind == Sk::Float && s.width == 2 => {
                Some(LocalType::Numeric(NumericType::Scalar(crate::Scalar::F32)))
            }
            TypeInner::Vector { size, scalar } if scalar.kind == Sk::Float && scalar.width == 2 => {
                Some(LocalType::Numeric(NumericType::Vector {
                    size,
                    scalar: crate::Scalar::F32,
                }))
            }
            _ => None,
        }
    }
}

impl crate::back::spv::recyclable::Recyclable for F16IoPolyfill {
    fn recycle(mut self) -> Self {
        self.io_var_to_f32_type = self.io_var_to_f32_type.recycle();
        self
    }
}
