# frozen_string_literal: true

require 'listen'

module Stylesheet
  class Watcher
    REDIS_KEY = "dev_last_used_theme_id"

    def self.theme_id=(v)
      Discourse.redis.set(REDIS_KEY, v)
    end

    def self.theme_id
      (Discourse.redis.get(REDIS_KEY) || SiteSetting.default_theme_id).to_i
    end

    def self.watch(paths = nil)
      watcher = new(paths)
      watcher.start
      watcher
    end

    def initialize(paths)
      @paths = paths || Watcher.default_paths
      @queue = Queue.new
    end

    def self.default_paths
      return @default_paths if @default_paths

      @default_paths = ["app/assets/stylesheets"]
      Discourse.plugins.each do |p|
        @default_paths << File.dirname(p.path).sub(Rails.root.to_s, '').sub(/^\//, '')
      end
      @default_paths
    end

    def start

      Thread.new do
        begin
          while true
            worker_loop
          end
        rescue => e
          STDERR.puts "CSS change notifier crashed #{e}"
          start
        end
      end

      root = Rails.root.to_s

      listener_opts = { ignore: /xxxx/ }
      listener_opts[:force_polling] = true if ENV['FORCE_POLLING']

      @paths.each do |watch|
        Thread.new do
          begin
            plugins_paths = Dir.glob("#{Rails.root}/plugins/*").map do |file|
              if File.symlink?(file)
                File.expand_path(File.readlink(file), "#{Rails.root}/plugins")
              else
                file
              end
            end.compact

            listener = Listen.to("#{root}/#{watch}", listener_opts) do |modified, added, _|
              paths = [modified, added].flatten
              paths.compact!
              paths.map! do |long|
                plugin_name = nil
                plugins_paths.each do |plugin_path|
                  if long.include?(plugin_path)
                    plugin_name = File.basename(plugin_path)
                    break
                  end
                end

                target = nil
                if !plugin_name
                  target_match = long.match(/admin|desktop|mobile|publish/)
                  if target_match&.length
                    target = target_match[0]
                  end
                end

                {
                  basename: File.basename(long),
                  target: target,
                  plugin_name: plugin_name
                }
              end

              process_change(paths)
            end
          rescue => e
            STDERR.puts "Failed to listen for CSS changes at: #{watch}\n#{e}"
          end
          listener.start
          sleep
        end
      end
    end

    def core_assets_refresh(target)
      targets = target ? [target] : ["desktop", "mobile", "admin"]
      Stylesheet::Manager.clear_core_cache!(targets)
      message = targets.map! do |name|
        Stylesheet::Manager.stylesheet_data(name.to_sym, Stylesheet::Watcher.theme_id)
      end.flatten!
      MessageBus.publish '/file-change', message
    end

    def plugin_assets_refresh(plugin_name)
      Stylesheet::Manager.clear_plugin_cache!(plugin_name)
      targets = [plugin_name]
      targets.push("#{plugin_name}_mobile") if DiscoursePluginRegistry.stylesheets_exists?(plugin_name, :mobile)
      targets.push("#{plugin_name}_desktop") if DiscoursePluginRegistry.stylesheets_exists?(plugin_name, :desktop)

      message = targets.map! do |name|
        Stylesheet::Manager.stylesheet_data(name.to_sym, Stylesheet::Watcher.theme_id)
      end.flatten!
      MessageBus.publish '/file-change', message
    end

    def worker_loop
      path = @queue.pop

      while @queue.length > 0
        @queue.pop
      end

      if path[:plugin_name]
        plugin_assets_refresh(path[:plugin_name])
      else
        core_assets_refresh(path[:target])
      end
    end

    def process_change(paths)
      paths.each do |path|
        if path[:basename] =~ /\.(css|scss)$/
          @queue.push path
        end
      end
    end
  end
end
