require 'diffy'

module Diffy
  class Diff
    # monkey patch diffy to fix buggy bug: https://github.com/samg/diffy/pull/103
    def diff_options
      Array(options[:context] ? "-U#{options[:context]}" : options[:diff])
    end
  end
end

module BooticCli
  module Themes
    # given :source and :target themes,
    # UpdatedTheme computes assets and templates
    # with more recent versions in :source
    class UpdatedTheme
      TemplateWithDiff = Struct.new('TemplateWithDiff', :file_name, :body, :updated_on, :diff)

      def initialize(source:, target:, force_update: false)
        @source, @target = source, target
        # when doing a pull or push, we don't care if the other end has a more recent version
        # we only do that when syncing changes, in which case force_update should be false
        @force_update = force_update
      end

      def any?
        templates.any? # || assets.any?
      end

      def templates
        @templates ||= map_pair(source.templates, target.templates) do |a, b|
          diff = Diffy::Diff.new(normalize_endings(b.body), normalize_endings(a.body), context: 1)

          if !diff.to_s.empty? && should_update?(a, b)
            c = TemplateWithDiff.new(a.file_name, a.body, a.updated_on, diff)
            [true, c]
          else
            [false, nil]
          end
        end
      end

      def assets
        @assets ||= map_pair(source.assets, target.assets) do |a, b|
          [should_update?(a, b), a]
        end
      end

      private
      attr_reader :source, :target, :force_update

      def should_update?(a, b)
        force_update || (a != b && more_recent?(a, b))
      end

      def more_recent?(a, b)
        a.updated_on > b.updated_on
      end

      def build_lookup(list)
        list.each_with_object({}) do |item, lookup|
          lookup[item.file_name] = item
        end
      end

      def map_pair(list1, list2, &block)
        lookup = build_lookup(list2)
        list1.each_with_object([]) do |item, arr|
          match = lookup[item.file_name]
          if match
            valid, item = yield(item, match)
            arr << item if valid
          end
        end
      end

      def normalize_endings(str)
        str.to_s.gsub(/\r\n?/, "\n")
      end
    end
  end
end