# frozen_string_literal: true

require "thread"

module Nokogiri
  module CSS
    class Parser < Racc::Parser # :nodoc:
      CACHE_SWITCH_NAME = :nokogiri_css_parser_cache_is_off

      @cache = {}
      @mutex = Mutex.new

      class << self
        # Return a thread-local boolean indicating whether the CSS-to-XPath cache is active. (Default is `true`.)
        def cache_on?
          !Thread.current[CACHE_SWITCH_NAME]
        end

        # Set a thread-local boolean to turn cacheing on and off. Truthy values turn the cache on, falsey values turn the cache off.
        def set_cache(value) # rubocop:disable Naming/AccessorMethodName
          Thread.current[CACHE_SWITCH_NAME] = !value
        end

        # Get the css selector in +string+ from the cache
        def [](string)
          return nil unless cache_on?
          @mutex.synchronize { @cache[string] }
        end

        # Set the css selector in +string+ in the cache to +value+
        def []=(string, value)
          return value unless cache_on?
          @mutex.synchronize { @cache[string] = value }
        end

        # Clear the cache
        def clear_cache(create_new_object = false)
          @mutex.synchronize do
            if create_new_object
              @cache = {}
            else
              @cache.clear
            end
          end
        end

        # Execute +block+ without cache
        def without_cache(&block)
          original_cache_setting = cache_on?
          set_cache(false)
          yield
        ensure
          set_cache(original_cache_setting)
        end
      end

      # Create a new CSS parser with respect to +namespaces+
      def initialize(namespaces = {})
        @tokenizer = Tokenizer.new
        @namespaces = namespaces
        super()
      end

      def parse(string)
        @tokenizer.scan_setup(string)
        do_parse
      end

      def next_token
        @tokenizer.next_token
      end

      # Get the xpath for +string+ using +options+
      def xpath_for(string, prefix, visitor)
        key = cache_key(string, prefix, visitor)
        self.class[key] ||= parse(string).map do |ast|
          ast.to_xpath(prefix, visitor)
        end
      end

      # On CSS parser error, raise an exception
      def on_error(error_token_id, error_value, value_stack)
        after = value_stack.compact.last
        raise SyntaxError, "unexpected '#{error_value}' after '#{after}'"
      end

      def cache_key(query, prefix, visitor)
        if self.class.cache_on?
          [query, prefix, @namespaces, visitor.config]
        end
      end
    end
  end
end
