require 'tempfile'
require 'securerandom'

FenceBlock = Struct.new(:attributes, :content)
BLOCK_QUOTE = '```'

def append_block(block, content, attribute, body)
  left = content[0, block.attributes[:'point-max']]
  right = content[block.attributes[:'point-max']..]
  left + "\n#{BLOCK_QUOTE}" + attribute + "\n" + body + "\n#{BLOCK_QUOTE}" + right
end

def extract_fence_blocks(site)
  site.scan(/#{BLOCK_QUOTE}(.*?)\n(.*?)\n#{BLOCK_QUOTE}/m).map do |attributes_str, content|
    attributes_str.chomp!
    language = attributes_str.scan(/^([^\s]+)/).first.first
    attributes = {language: language}

    attributes_str.scan(/@([^\s]+)/) do |matches|
      attributes[matches[0].to_sym] = true
    end

    attributes_str.scan(/([^\s:]+):([^\s]+)/) do |matches|
      attributes[matches[0].to_sym] = matches[1].strip
    end

    point_min = site.index("#{BLOCK_QUOTE}#{attributes_str}\n#{content}\n#{BLOCK_QUOTE}")
    # +1 = \n
    # +6 = ```..```
    point_max = point_min + attributes_str.size + 1 + content.size + 1 + (BLOCK_QUOTE.size * 2)
    attributes[:'point-min'] = point_min
    attributes[:'point-max'] = point_max

    FenceBlock.new(attributes, content)
  end
end

def execute_block(block, app_dir)
  executable = block.attributes.fetch(:executable, "sh")
  filename = block.attributes.fetch(:file, SecureRandom.hex(10))
  chdir = block.attributes.fetch(:chdir, "")
  app_file = File.join(app_dir, filename)

  Dir.chdir(app_dir) do
    File.write(filename, block.content)
    Tempfile.open('command-out') do |file|
      Dir.chdir(File.join(app_dir, chdir)) do
        system("#{executable} '#{app_file}'", out: file)
      end
      file.rewind

      if !block.attributes.fetch(:silence, false)
        puts file.read
      end
    end
  end
end

def render_block(block, app_dir)
  filename = block.attributes[:file]
  app_file = File.join(app_dir, filename)
  FileUtils.mkdir_p(File.dirname(app_file))
  open_mode = 'a'
  open_mode = 'w' if block.attributes.fetch(:create, false)
  File.open(app_file, open_mode) do |file|
    file.write(block.content)
    file.write("\n")
  end
end

# publish
def mdtoapp(content, app_dir:, mode: nil)
  blocks = extract_fence_blocks(content)
             .reject { |b| b.attributes.fetch(:mdtoapp, false) == false }

  as_artifact = mode == :artifact
  as_tdd = mode == :tdd

  last_executable_block = nil
  # ejecutar
  blocks
    .each do |block|
    for_artifact = block.attributes.fetch(:artifact, false)
    for_tdd = block.attributes.fetch(:tdd, false)
    # DEPRECATED hook:execute 
    is_executable = block.attributes.fetch(:hook, nil) == 'execute' || block.attributes.fetch(:execute, false)

    if is_executable
      next if as_artifact && for_artifact == false
      last_executable_block = block
      if (as_tdd && for_tdd) || for_artifact == true || mode.nil?
        execute_block(block, app_dir)
      end
    elsif !block.attributes.fetch(:file, nil).nil?
      render_block(block, app_dir)
    end
  end

  if as_tdd
    execute_block(last_executable_block, app_dir)
  end
end

# script

if __FILE__==$0
  if ENV['TO']
    FileUtils.mkdir_p(ENV['TO'])
    dir = ENV['TO']
  else
    dir = Dir.mktmpdir()
  end

  begin
    source = ARGF.read
  rescue Errno::ENOENT
    require 'open-uri'
    if ARGF.filename.start_with?('https://chiselapp.com/user/bit4bit')
      source = URI.open(ARGF.filename).read
    else
      raise 'invalid file markdown'
    end
  end


  mode = case true
         # al habilitar TDD se ejecuta el ultimo bloque ejecutable
         # y todos los marcados como @tdd
         when ENV['TDD'].nil? == false
           :tdd
         # al habilitar ARTIFACT solo se ejecutan los bloques marcados como @artifact
         # si generan los archivos
         when ENV['ARTIFACT'].nil? == false
           :artifact
         else
           nil
         end

  mdtoapp(source, app_dir: dir, mode: mode)
end
