require "haml_i18n_lint/ruby_parser"

module HamlI18nLint
  class Linter
    module CompilerExtension

      def compile_script
        super
        lint_script(@node.value[:text])
      end

      def compile_plain
        super
        text = @node.value[:text]
        lint_add(text) if lint_config.need_i18n?(text)
      end

      def lint_attributes_hashes
        @node.value[:attributes_hashes]
      end

      def lint_attributes
        lint_attributes_hashes.any? do |attributes_hash|
          lint_script("{#{attributes_hash}}")
        end
      end

      def compile_tag
        super
        if @node.value[:parse]
          lint_script(@node.value[:value])
        else
          value = @node.value[:value]
          lint_add(value) if lint_config.need_i18n?(value)
        end

        placeholder = @node.value.dig(:attributes, 'placeholder') || ""
        lint_add(placeholder) if lint_config.need_i18n?(placeholder)

        value = @node.value.dig(:attributes, 'value') || ""
        lint_add(value) if lint_config.need_i18n?(value)

        lint_attributes
      end

      private

      def lint_aref?(sexp)
        sexp.first == :aref
      end

      def lint_assoc_new?(sexp, key)
        return false unless sexp.first == :assoc_new

        _assoc_new, assoc_key, _value = sexp

        type, k, _lineno = assoc_key

        case type
        when :@label
          return k == "#{key}:"
        when :dyna_symbol
          return k.size == 2 &&
                 k.first == :string_content &&
                 k.last[0] == :@tstring_content &&
                 k.last[1] == key
        end

        false
      end

      def lint_command?(sexp, method_name)
        return unless sexp.first == :command

        _command, (ident, m, _lineno), * = sexp

        ident == :@ident && m == method_name
      end

      def lint_fcall?(sexp, method_name)
        return unless sexp.first == :method_add_arg

        _method_add_arg, (fcall, (ident, m, _lineno)), * = sexp

        fcall == :fcall && ident == :@ident && m == method_name
      end

      def lint_call?(sexp, method_name)
        return unless sexp.first == :method_add_arg

        _method_add_arg, (call, _receiver, _dot, (ident, m, _lineno)), * = sexp

        call == :call && ident == :@ident && m == method_name
      end

      def lint_string_literal?(sexp)
        sexp.first == :string_literal
      end

      def lint_string_literal_need_i18n?(sexp)
        exps = sexp.flatten
        exps.each_with_index.any? do |exp, i|
          exp == :@tstring_content && exps[i + 1].is_a?(String) && lint_config.need_i18n?(exps[i + 1])
        end
      end

      def lint_ignore_method?(sexp, m)
        lint_command?(sexp, m) || lint_fcall?(sexp, m) || lint_call?(sexp, m)
      end

      def lint_script(script)
        script = script.dup
        if Ripper.lex(script.rstrip).any? { |(_, on_kw, kw_do)| on_kw == :on_kw && kw_do == "do" }
          script << "\nend\n"
        end
        sexp = RubyParser.sexp(script)

        string_literal_found = false
        walk = -> (sexp) do
          return if !sexp.is_a?(Array)
          return if lint_aref?(sexp)
          return if lint_config.ignore_methods.any? { |m| lint_ignore_method?(sexp, m) }
          return if lint_config.ignore_keys.any? { |k| lint_assoc_new?(sexp, k) }

          if lint_string_literal?(sexp) && lint_string_literal_need_i18n?(sexp)
            string_literal_found = sexp[1]
            return
          end

          sexp.each do |sexp|
            walk.(sexp)
          end
        end

        walk.(sexp)

        lint_add(string_literal_found) if string_literal_found
      end

    end
  end
end