module Schemacop
  module V3
    class StringNode < Node
      ATTRIBUTES = %i[
        min_length
        max_length
        format
      ].freeze

      def self.allowed_options
        super + ATTRIBUTES + %i[format_options pattern allow_blank]
      end

      def allowed_types
        { String => :string }
      end

      def as_json
        json = { type: :string }
        if options[:pattern]
          json[:pattern] = V3.sanitize_exp(Regexp.compile(options[:pattern]))
        end
        process_json(ATTRIBUTES, json)
      end

      def _validate(data, result:)
        super_data = super

        # Validate blank #
        if options[:allow_blank].is_a?(FalseClass) && super_data.blank?
          result.error 'String is blank but must not be blank!'
        end

        return if super_data.nil?

        # Validate length #
        length = super_data.size

        if options[:min_length] && length < options[:min_length]
          result.error "String is #{length} characters long but must be at least #{options[:min_length]}."
        end

        if options[:max_length] && length > options[:max_length]
          result.error "String is #{length} characters long but must be at most #{options[:max_length]}."
        end

        # Validate pattern #
        if (pattern = options[:pattern])
          unless options[:pattern].is_a?(Regexp)
            pattern = Regexp.compile(pattern)
          end

          unless super_data.match?(pattern)
            result.error "String does not match pattern #{V3.sanitize_exp(pattern).inspect}."
          end
        end

        # Validate format #
        if options[:format] && Schemacop.string_formatters.include?(options[:format])
          pattern = Schemacop.string_formatters[options[:format]][:pattern]

          if pattern && !super_data.match?(pattern)
            result.error "String does not match format #{options[:format].to_s.inspect}."
          elsif options[:format_options] && Node.resolve_class(options[:format])
            node = create(options[:format], **options[:format_options])
            node._validate(cast(super_data), result: result)
          end
        end
      end

      def cast(value)
        if !value.nil?
          to_cast = value
        elsif default.present?
          to_cast = default
        else
          return nil
        end

        if (handler = Schemacop.string_formatters.dig(options[:format], :handler))
          return handler.call(to_cast)
        else
          return to_cast
        end
      end

      protected

      def init
        if options.include?(:format)
          options[:format] = options[:format].to_s.dasherize.to_sym
        end
      end

      def validate_self
        if options.include?(:format) && !Schemacop.string_formatters.include?(options[:format])
          fail "Format #{options[:format].to_s.inspect} is not supported."
        end

        unless options[:min_length].nil? || options[:min_length].is_a?(Integer)
          fail 'Option "min_length" must be an "integer"'
        end

        unless options[:max_length].nil? || options[:max_length].is_a?(Integer)
          fail 'Option "max_length" must be an "integer"'
        end

        if options[:min_length] && options[:max_length] && options[:min_length] > options[:max_length]
          fail 'Option "min_length" can\'t be greater than "max_length".'
        end

        if options[:pattern]
          unless options[:pattern].is_a?(String) || options[:pattern].is_a?(Regexp)
            fail 'Option "pattern" must be a string or Regexp.'
          end

          begin
            Regexp.compile(options[:pattern])
          rescue RegexpError => e
            fail "Option \"pattern\" can't be parsed: #{e.message}."
          end
        end
      end
    end
  end
end