class CancerRegistryReportingTestKit::HdeaGenerator::MustSupportMetadataExtractor

def all_must_support_elements

def all_must_support_elements
  profile_elements.select do |element| 
    element.path != resource && (element.mustSupport || is_uscdi_requirement_element?(element))
  end
end

def discriminators(slice)

def discriminators(slice)
  slice&.slicing&.discriminator
end

def element_part_of_slice_discrimination?(element)

def element_part_of_slice_discrimination?(element)
  must_support_slice_elements.any? { |ms_slice| element.id.include?(ms_slice.id) }
end

def get_type_must_support_metadata(current_metadata, current_element)

def get_type_must_support_metadata(current_metadata, current_element)
  current_element.type.map do |type|
    next unless type_must_support_extension?(type.extension)
    metadata =
      {
        path: "#{current_metadata[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
        original_path: current_metadata[:path]
      }
    metadata[:types] = [type.code] if save_type_code?(type)
    handle_type_must_support_target_profiles(type, metadata) if type.code == 'Reference'
    metadata
  end.compact
end

def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)

def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
  choice_element_metadata = must_support_elements_metadata.find do |metadata|
    metadata[:original_path].present? &&
      current_metadata[:path].include?(metadata[:original_path])
  end
  return unless choice_element_metadata.present?
  current_metadata[:original_path] = current_metadata[:path]
  current_metadata[:path] =
    current_metadata[:path].sub(choice_element_metadata[:original_path], choice_element_metadata[:path])
end

def handle_fixed_values(metadata, element)

def handle_fixed_values(metadata, element)
  if element.fixedUri.present?
    metadata[:fixed_value] = element.fixedUri
  elsif element.patternCodeableConcept.present? && !element_part_of_slice_discrimination?(element)
    metadata[:fixed_value] = element.patternCodeableConcept.coding.first.code
    metadata[:path] += '.coding.code'
  elsif element.fixedCode.present?
    metadata[:fixed_value] = element.fixedCode
  elsif element.patternIdentifier.present? && !element_part_of_slice_discrimination?(element)
    metadata[:fixed_value] = element.patternIdentifier.system
    metadata[:path] += '.system'
  end
end

def handle_type_must_support_target_profiles(type, metadata)

def handle_type_must_support_target_profiles(type, metadata)
  # US Core 3.1.1 profiles do not have US Core target profiles.
  # Vital Sign proifles from FHIR R4 (version 4.0.1) do not have US Core target profiles either.
  return if ['3.1.1', '4.0.1'].include?(profile.version)
  target_profiles = []
  if type.targetProfile&.length == 1
    target_profiles << type.targetProfile.first
  else
    type.source_hash['_targetProfile']&.each_with_index do |hash, index|
      if hash.present?
        element = FHIR::Element.new(hash)
        target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
      end
    end
  end
  # remove target_profile for FHIR Base resource type.
  target_profiles.delete_if { |reference| reference.start_with?('http://hl7.org/fhir/StructureDefinition') }
  metadata[:target_profiles] = target_profiles if target_profiles.present?
end

def initialize(profile_elements, profile, resource, ig_resources)

def initialize(profile_elements, profile, resource, ig_resources)
  self.profile_elements = profile_elements
  self.profile = profile
  self.resource = resource
  self.ig_resources = ig_resources
end

def is_blood_pressure?

def is_blood_pressure?
  %w[bp us-core-blood-pressure us-core-average-blood-pressure].include?(profile.id)
end

def is_uscdi_requirement_element?(element)

def is_uscdi_requirement_element?(element)
  element.extension.any? do |extension|
    extension.url == 'http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement' &&
      extension.valueBoolean
  end && !element.mustSupport
end

def is_vital_sign?

def is_vital_sign?
  [
    'http://hl7.org/fhir/StructureDefinition/vitalsigns',
    'http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs'
  ].include?(profile.baseDefinition)
end

def must_support_elements

def must_support_elements
  plain_must_support_elements.each_with_object([]) do |current_element, must_support_elements_metadata|
    {
      path: current_element.id.gsub("#{resource}.", '')
    }.tap do |current_metadata|
      current_metadata[:uscdi_only] = true if is_uscdi_requirement_element?(current_element)
      type_must_support_metadata = get_type_must_support_metadata(current_metadata, current_element)
      if type_must_support_metadata.any?
        must_support_elements_metadata.concat(type_must_support_metadata)
      else
        handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
        supported_types = current_element.type.select { |type| save_type_code?(type) }.map(&:code)
        current_metadata[:types] = supported_types if supported_types.present?
        if current_element.type.first&.code == 'Reference'
          handle_type_must_support_target_profiles(current_element.type.first,
                                                   current_metadata)
        end
        handle_fixed_values(current_metadata, current_element)
        must_support_elements_metadata.delete_if do |metadata|
          metadata[:path] == current_metadata[:path] && metadata[:fixed_value].blank?
        end
        must_support_elements_metadata << current_metadata
      end
    end
  end.uniq
end

def must_support_extension_elements

def must_support_extension_elements
  all_must_support_elements.select { |element| element.path.end_with? 'extension' }
end

def must_support_extensions

def must_support_extensions
  must_support_extension_elements.map do |element|
    next unless element.type.first.profile.present?
    {
      id: element.id,
      path: element.path.gsub("#{resource}.", ''),
      url: element.type.first.profile.first
    }.tap do |metadata|
      metadata[:uscdi_only] = true if is_uscdi_requirement_element?(element)
    end
  end.compact
end

def must_support_pattern_slice_elements

def must_support_pattern_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element))&.first&.type == 'pattern'
  end
end

def must_support_slice_elements

def must_support_slice_elements
  all_must_support_elements.select do |element|
    !element.path.end_with?('extension') && element.sliceName.present?
  end
end

def must_support_slices

def must_support_slices
  pattern_slices + type_slices + value_slices
end

def must_support_type_slice_elements

def must_support_type_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element))&.first&.type == 'type'
  end
end

def must_support_value_slice_elements

def must_support_value_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element))&.first&.type == 'value'
  end
end

def must_supports

def must_supports
  @must_supports = {
    extensions: must_support_extensions,
    slices: must_support_slices,
    elements: must_support_elements
  }
  @must_supports
end

def pattern_slices

def pattern_slices
  must_support_pattern_slice_elements.map do |current_element|
    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", '')
    }.tap do |metadata|
      discriminator = discriminators(sliced_element(current_element)).first
      discriminator_path = discriminator.path
      discriminator_path = '' if discriminator_path == '$this'
      pattern_element =
        if discriminator_path.present?
          profile_elements.find { |element| element.id == "#{current_element.id}.#{discriminator_path}" }
        else
          current_element
        end
      metadata[:discriminator] =
        if pattern_element.patternCodeableConcept.present?
          {
            type: 'patternCodeableConcept',
            path: discriminator_path,
            code: pattern_element.patternCodeableConcept.coding.first.code,
            system: pattern_element.patternCodeableConcept.coding.first.system
          }
        elsif pattern_element.patternCoding.present?
          {
            type: 'patternCoding',
            path: discriminator_path,
            code: pattern_element.patternCoding.code,
            system: pattern_element.patternCoding.system
          }
        elsif pattern_element.patternIdentifier.present?
          {
            type: 'patternIdentifier',
            path: discriminator_path,
            system: pattern_element.patternIdentifier.system
          }
        elsif pattern_element.binding&.strength == 'required' &&
              pattern_element.binding&.valueSet.present?
          value_extractor = ValueExactor.new(ig_resources, resource, profile_elements)
          values = value_extractor.codings_from_value_set_binding(pattern_element).presence ||
                   value_extractor.values_from_resource_metadata([metadata[:path]]).presence || []
          {
            type: 'requiredBinding',
            path: discriminator_path,
            values: values
          }
        else
          raise StandardError, 'Unsupported discriminator pattern type'
        end
      metadata[:uscdi_only] = true if is_uscdi_requirement_element?(current_element)
    end
  end
end

def plain_must_support_elements

def plain_must_support_elements
  all_must_support_elements - must_support_extension_elements - must_support_slice_elements
end

def remove_blood_pressure_value_data_absent_reason

Exclude Observation.value[x] from observation-bp
def remove_blood_pressure_value_data_absent_reason
  return unless is_blood_pressure?
  pattern = /component(:[^.]+)?\.dataAbsentReason/
  @must_supports[:elements].delete_if do |element|
    element[:path].start_with?('value[x]') ||
      element[:original_path]&.start_with?('value[x]') ||
      element[:path] == ('dataAbsentReason') ||
      (
        pattern.match?(element[:path]) && ['3.1.1', '4.0.0'].include?(ig_resources.ig.version)
      )
  end
  @must_supports[:slices].delete_if do |slice|
    slice[:path].start_with?('value[x]')
  end
end

def remove_observation_data_absent_reason

Since US Core 5.0.1, Blood Pressure profile restores component.dataAbsentReason as MustSupport.
Smoking status profile does not have MS on dataAbsentReason. It is safe to use profile.type == 'Observation'
Remove MS check for dataAbsentReason and component.dataAbsentReason from vital sign profiles and observation lab profile
are not required to demonstrate Health IT Module support for "dataAbsentReason" elements.
ONC and US Core 4.0.0 both clarified that health IT developers that always provide HL7 FHIR "observation" values
def remove_observation_data_absent_reason
  return if is_blood_pressure?
  pattern = /(component(:[^.]+)?\.)?dataAbsentReason/
  return unless profile.type == 'Observation'
  @must_supports[:elements].delete_if do |element|
    pattern.match?(element[:path])
  end
end

def remove_vital_sign_component

Exclude Observation.component from vital sign profiles except observation-bp and observation-pulse-ox
def remove_vital_sign_component
  return if is_blood_pressure? || profile.name == 'USCorePulseOximetryProfile'
  return unless is_vital_sign?
  @must_supports[:elements].delete_if do |element|
    element[:path].start_with?('component')
  end
end

def save_type_code?(type)

def save_type_code?(type)
  type.code == 'Reference'
end

def sliced_element(slice)

def sliced_element(slice)
  profile_elements.find do |element|
    element.id == slice.path || element.id == slice.id.sub(":#{slice.sliceName}", '')
  end
end

def type_must_support_extension?(extensions)

def type_must_support_extension?(extensions)
  extensions&.any? do |extension|
    extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
      extension.valueBoolean
  end
end

def type_slices

def type_slices
  must_support_type_slice_elements.map do |current_element|
    discriminator = discriminators(sliced_element(current_element)).first
    type_path = discriminator.path
    type_path = '' if type_path == '$this'
    type_element =
      if type_path.present?
        profile_elements.find { |element| element.id == "#{current_element.id}.#{type_path}" }
      else
        current_element
      end
    type_code = type_element.type.first.code
    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", ''),
      discriminator: {
        type: 'type',
        code: type_code.upcase_first
      }
    }.tap do |metadata|
      metadata[:uscdi_only] = true if is_uscdi_requirement_element?(current_element)
    end
  end
end

def value_slices

def value_slices
  must_support_value_slice_elements.map do |current_element|
    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", ''),
      discriminator: {
        type: 'value'
      }
    }.tap do |metadata|
      metadata[:discriminator][:values] = discriminators(sliced_element(current_element)).map do |discriminator|
        fixed_element = profile_elements.find do |element|
          element.id.starts_with?(current_element.id) &&
            element.path == "#{current_element.path}.#{discriminator.path}"
        end
        if fixed_element.patternCodeableConcept.present?
          code_value = fixed_element.patternCodeableConcept.coding.first.code
          {
            path: "#{discriminator.path}.coding.code",
            value: code_value !~ /\D/ ? code_value.to_i : code_value
          }
        else
          {
            path: discriminator.path,
            value: fixed_element.fixedUri || fixed_element.fixedCode
          }
        end
        
      end
      metadata[:uscdi_only] = true if is_uscdi_requirement_element?(current_element)
    end
  end
end