text: require 'nokogiri'
  require 'erb'
  require 'ostruct'
  require 'action_mailer'
  require 'premailer'
  require 'premailer/rails'
  require 'rails'
  
  module BootstrapEmail
    class Compiler
      def initialize(mail)
        @mail = mail
        @source = mail.html_part || mail
        update_doc(@source.body.raw_source)
      end
  
      def update_doc(source)
        @doc = Nokogiri::HTML(source)
      end
  
      def perform_full_compile
        compile_html!
        inline_css!
        inject_head!
        update_mailer!
      end
  
      def compile_html!
        button
        badge
        alert
        card
        hr
        container
        grid
        align
        padding
        margin
        spacer
        table
        body
      end
  
      def inline_css!
        @source.body = @doc.to_html
        @mail = Premailer::Rails::Hook.perform(@mail)
        @mail.header[:skip_premailer] = true
        update_doc(@mail.html_part.body.raw_source)
      end
  
      def inject_head!
        @doc.at_css('head').add_child(bootstrap_email_head)
      end
  
      def update_mailer!
        @mail.html_part.body = @doc.to_html
        @mail
      end
  
      private
  
      def bootstrap_email_head
        engine = defined?(SassC::Engine).nil? ? Sass::Engine : SassC::Engine
        html_string = <<-HEREDOC
          <style type="text/css">
            #{engine.new(File.open(File.expand_path('../core/head.scss', __dir__)).read, syntax: :scss, style: :compressed, cache: false, read_cache: false).render}
          </style>
        HEREDOC
        html_string
      end
  
      def template(file, locals_hash = {})
        namespace = OpenStruct.new(locals_hash)
        template_html = File.open(File.expand_path("../core/templates/#{file}.html.erb", __dir__)).read
        ERB.new(template_html).result(namespace.instance_eval { binding })
      end
  
      def each_node(css_lookup, &blk)
        # sort by youngest child and traverse backwards up the tree
        @doc.css(css_lookup).sort_by { |n| n.ancestors.size }.reverse!.each(&blk)
      end
  
      def button
        each_node('.btn') do |node| # move all classes up and remove all classes from the element
          node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.to_html))
        end
      end
  
      def badge
        each_node('.badge') do |node| # move all classes up and remove all classes from the element
          node.replace(template('table-left', classes: node['class'], contents: node.delete('class') && node.to_html))
        end
      end
  
      def alert
        each_node('.alert') do |node| # move all classes up and remove all classes from the element
          node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.to_html))
        end
      end
  
      def align
        each_node('.float-left') do |node|
          align_helper(node, /float-left/, 'left')
        end
        each_node('.mx-auto') do |node|
          align_helper(node, /mx-auto/, 'center')
        end
        each_node('.float-right') do |node|
          align_helper(node, /float-right/, 'right')
        end
      end
  
      def align_helper(node, klass, template)
        if node.name != 'table' # if it is already on a table, set the proprieties on the current table
          node['class'] = node['class'].sub(klass, '')
          node.replace(template("align-#{template}", contents: node.to_html))
        else
          node['align'] = template
        end
      end
  
      def card
        each_node('.card') do |node| # move all classes up and remove all classes from element
          node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.to_html))
        end
        each_node('.card-body') do |node| # move all classes up and remove all classes from element
          node.replace(template('table', classes: node['class'], contents: node.delete('class') && node.to_html))
        end
      end
  
      def hr
        each_node('hr') do |node| # drop hr in place of current
          node.replace(template('hr', classes: "hr #{node['class']}"))
        end
      end
  
      def container
        each_node('.container') do |node|
          node.replace(template('container', classes: node['class'], contents: node.inner_html))
        end
        each_node('.container-fluid') do |node|
          node.replace(template('table', classes: node['class'], contents: node.inner_html))
        end
      end
  
      def grid
        each_node('.row') do |node|
          node.replace(template('row', classes: node['class'], contents: node.inner_html))
        end
        each_node('*[class*=col]') do |node|
          node.replace(template('col', classes: node['class'], contents: node.inner_html))
        end
      end
  
      def padding
        each_node('*[class*=p-], *[class*=pt-], *[class*=pr-], *[class*=pb-], *[class*=pl-], *[class*=px-], *[class*=py-]') do |node|
          next unless node.name != 'table' # if it is already on a table, set the padding on the table, else wrap the content in a table
  
          padding_regex = /(p[trblxy]?-\d)/
          classes = node['class'].scan(padding_regex).join(' ')
          node['class'] = node['class'].gsub(padding_regex, '')
          node.replace(template('table', classes: classes, contents: node.to_html))
        end
      end
  
      def margin
        each_node('*[class*=my-], *[class*=mt-], *[class*=mb-]') do |node|
          top_class = node['class'][/m[ty]{1}-(lg-)?(\d)/]
          bottom_class = node['class'][/m[by]{1}-(lg-)?(\d)/]
          node['class'] = node['class'].gsub(/(m[tby]{1}-(lg-)?\d)/, '')
          html = ''
          if top_class
            html += template('div', classes: "s-#{top_class.gsub(/m[ty]{1}-/, '')}", contents: nil)
          end
          html += node.to_html
          if bottom_class
            html += template('div', classes: "s-#{bottom_class.gsub(/m[by]{1}-/, '')}", contents: nil)
          end
          node.replace(html)
        end
      end
  
      def spacer
        spacers = {
          '0' => 0,
          '1' => (16 * 0.25),
          '2' => (16 * 0.5),
          '3' => 16,
          '4' => (16 * 1.5),
          '5' => (16 * 3)
        }
        each_node('*[class*=s-]') do |node|
          temp = Nokogiri::HTML::DocumentFragment.parse(template('table', classes: node['class'] + ' w-100', contents: '&nbsp;'))
          temp.at_css('td')['height'] = spacers[node['class'].gsub(/s-/, '')].to_i
          node.replace(temp)
        end
      end
  
      def table
        @doc.css('table').each do |node|
          # border="0" cellpadding="0" cellspacing="0"
          node['border'] = 0
          node['cellpadding'] = 0
          node['cellspacing'] = 0
        end
      end
  
      def body
        @doc.css('body').each do |node|
          node.replace('<body>' + preview_text.to_s + template('body', classes: "#{node['class']} body", contents: node.inner_html.to_s) + '</body>')
        end
      end
  
      def preview_text
        preview_node = @doc.at_css('preview')
        return if preview_node.blank?
  
        # apply spacing after the text max of 100 characters so it doesn't show body text
        preview_node.content += '&nbsp;' * [(100 - preview_node.content.length.to_i), 0].max
        node = template('div', classes: 'preview', contents: preview_node.content)
        preview_node.remove
        node
      end
    end
  end
  
  require 'bootstrap-email/premailer_railtie'
  require 'bootstrap-email/action_mailer'
  require 'bootstrap-email/engine'
  require 'bootstrap-email/version'
  