module ActiveRecord
  module FullTextSearch
    register :schema_dumper do
      require "active_record/connection_adapters/postgresql/schema_dumper"
      ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend SchemaDumper
    end

    module SchemaDumper
      private

      def extensions(stream)
        super
        functions(stream)
        text_search_parsers(stream)
        text_search_templates(stream)
        text_search_dictionaries(stream)
        text_search_configurations(stream)
      end

      def tables(stream)
        super
        triggers(stream)
      end

      def functions(stream)
        return unless (functions = @connection.functions).any?

        stream.puts "  # These are functions that must be created in order to support this database"

        functions.each do |name, definition|
          source = definition.delete(:source)
          arguments = definition.delete(:arguments)
          stream.puts %{  create_function "#{name}(#{arguments})", #{hash_to_string(definition)}, as: <<~SQL}
          stream.puts source.strip.gsub(/^/, "    ").gsub(/\s+$/, "")
          stream.puts "  SQL"
        end

        stream.puts
      end

      def triggers(stream)
        return unless (triggers = @connection.triggers).any?

        stream.puts
        stream.puts "  # These are triggers that must be created in order to support this database"

        triggers.each do |name, definition|
          definition[:name] = name if include_trigger_name_in_dump?(definition)
          table = definition.delete(:table)
          function = definition.delete(:function)
          stream.puts %(  create_trigger #{table.inspect}, #{function.inspect}, #{hash_to_string(definition)})
        end

        # stream.puts
      end

      def text_search_parsers(stream)
        return unless (parsers = @connection.text_search_parsers).any?

        stream.puts "  # These are full-text search parsers that must be created in order to support this database"

        parsers.each do |name, definition|
          stream.puts %(  create_text_search_parser "#{name}", #{hash_to_string(definition)})
        end

        stream.puts
      end

      def text_search_templates(stream)
        return unless (templates = @connection.text_search_templates).any?

        stream.puts "  # These are full-text search templates that must be created in order to support this database"

        templates.each do |name, definition|
          stream.puts %(  create_text_search_template "#{name}", #{hash_to_string(definition)})
        end
        stream.puts
      end

      def text_search_dictionaries(stream)
        return unless (dictionaries = @connection.text_search_dictionaries).any?

        stream.puts "  # These are full-text search dictionaries that must be created in order to support this database"

        dictionaries.each do |name, definition|
          stream.puts %(  create_text_search_dictionary "#{name}", #{hash_to_string(definition)})
        end

        stream.puts
      end

      def text_search_configurations(stream)
        return unless (configurations = @connection.text_search_configurations).any?

        stream.puts "  # These are full-text search configurations that must be created in order to support this database"

        configurations.each do |name, definition|
          if definition[:parser] == "default"
            stream.puts %(  create_text_search_configuration "#{name}")
          else
            stream.puts %(  create_text_search_configuration "#{name}", parser: "#{definition[:parser]}")
          end
          definition[:maps].each do |dicts, tokens|
            stream.puts %(  add_text_search_configuration_mapping "#{name}", #{array_to_string(tokens)}, #{array_to_string(dicts)})
          end
        end
        stream.puts
      end

      private

      def include_trigger_name_in_dump?(trigger_definition)
        trigger_definition[:name] !=~ /\Atg_rails_/
      end

      def hash_to_string(hash)
        hash.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
      end

      def array_to_string(array)
        return array.inspect if array.detect { |v| !v.is_a?(String) || v =~ /\s+/ }

        "%w[#{array.join(" ")}]"
      end
    end
  end
end