lib/canon/comparison/strategies/base_match_strategy.rb



# frozen_string_literal: true

module Canon
  module Comparison
    module Strategies
      # Abstract base class for match strategies
      #
      # All match strategies must inherit from this class and implement:
      # - match(doc1, doc2) → Array<DiffNode>
      # - preprocess_for_display(doc1, doc2) → [String, String]
      #
      # This provides a common interface for different matching algorithms,
      # enabling the Strategy Pattern for extensible comparison methods.
      #
      # @example Create a custom match strategy
      #   class MyMatchStrategy < BaseMatchStrategy
      #     def match(doc1, doc2)
      #       # Custom matching logic
      #       # Must return Array<Canon::Diff::DiffNode>
      #     end
      #
      #     def preprocess_for_display(doc1, doc2)
      #       # Format documents for diff display
      #       # Must return [String, String]
      #     end
      #   end
      #
      class BaseMatchStrategy
        attr_reader :format, :match_options

        # Initialize strategy
        #
        # @param format [Symbol] Document format (:xml, :html, :json, :yaml)
        # @param match_options [Hash] Match options for comparison
        def initialize(format:, match_options:)
          @format = format
          @match_options = match_options
        end

        # Perform matching and return DiffNodes
        #
        # This is the core method that implements the matching algorithm.
        # All strategies must implement this to produce DiffNodes that
        # flow through the standard diff rendering pipeline.
        #
        # @param doc1 [Object] First document
        # @param doc2 [Object] Second document
        # @return [Array<Canon::Diff::DiffNode>] Array of differences
        # @raise [NotImplementedError] If not implemented by subclass
        def match(doc1, doc2)
          raise NotImplementedError,
                "#{self.class} must implement #match(doc1, doc2)"
        end

        # Preprocess documents for display in diff output
        #
        # This method formats the documents into strings suitable for
        # line-by-line diff display. The format must be consistent across
        # all strategies for the same format to ensure the diff rendering
        # pipeline produces correct output.
        #
        # @param doc1 [Object] First document
        # @param doc2 [Object] Second document
        # @return [Array<String>] Preprocessed [doc1_string, doc2_string]
        # @raise [NotImplementedError] If not implemented by subclass
        def preprocess_for_display(doc1, doc2)
          raise NotImplementedError,
                "#{self.class} must implement #preprocess_for_display(doc1, doc2)"
        end

        # Optional metadata to include in ComparisonResult
        #
        # Subclasses can override this to provide algorithm-specific
        # metadata such as statistics, configuration, etc.
        #
        # @return [Hash] Additional metadata
        def metadata
          {}
        end

        # Algorithm name derived from class name
        #
        # Automatically generates algorithm identifier from class name.
        # For example:
        # - DomMatchStrategy → :dom
        # - SemanticTreeMatchStrategy → :semantic_tree
        #
        # @return [Symbol] Algorithm identifier
        def algorithm_name
          self.class.name.split("::").last
            .gsub("MatchStrategy", "")
            .gsub(/([A-Z])/, '_\1')
            .downcase[1..]
            .to_sym
        end
      end
    end
  end
end