# frozen_string_literal: true

# allow to access Atome class using only a uniq atome
class Genesis
  class << self

    def create_particle(element, store, render)
      Atome.define_method "set_#{element}" do |params, &user_proc|
       particle_creation(element, params, store, render, &user_proc)
      end
    end

    def build_particle(particle_name, options = {}, &particle_proc)
      type = options[:type]
      category = options[:category]
      type = :string if options[:type].nil?
      store = options[:store]
      store = true if options[:store].nil?
      render = options[:render]
      render = true if options[:render].nil?

      # we add the new method to the particle's collection of methods
      Universe.add_to_particle_list(particle_name, type, category)
      # the line below create an empty particle method for each renderer, eg: browser_left, headless_width, ...
      # the line below create the corresponding particle method for Batch class
      # particle_method_for_batch(particle_name)
      auto_render_generator(particle_name) if render
      new_particle(particle_name, store, render, &particle_proc)
      # the line below create all alternatives methods such as create 'method='
      additional_particle_methods(particle_name, store, render, &particle_proc)
    end

    def build_atome(atome_name, &atome_proc)
      # we add the new method to the atome's collection of methods
      Universe.add_to_atome_list(atome_name)
      # the method below generate Atome method creation at Object level,
      # so a syntax like : 'text(:hello)' is possible instead of the mandatory : grab(:view).text(:hello)
      atome_method_for_object(atome_name)
      # the line below create the corresponding atome method for Batch class
      # atome_method_for_batch(atome_name)
      unless Essentials.default_params[atome_name]
        # we create default params for the new created atome, adding the hash to : module essential, @default_params
        # FIXME : the hash : attach: [:view] means that newly atome will systematically be attached to the wview instaed of the parent:
        # ex : b.www will attach to view not b!
        Essentials.new_default_params(atome_name => { type: atome_name })
      end

      # the line below create an empty atome method for each renderer, eg: browser_shape, headless_color, ...
      auto_render_generator(atome_name)
      new_atome(atome_name, &atome_proc)

    end

    def auto_render_generator(element)
      Universe.renderer_list.each do |render_engine|
        build_render("#{render_engine}_#{element}",)
      end
    end

    def build_render(renderer_name, &method_proc)
      Atome.define_method renderer_name do |params = nil, &user_proc|
        instance_exec(params, user_proc, &method_proc) if method_proc.is_a?(Proc)
      end
    end

    def build_sanitizer(method_name, &method_proc)
      Universe.add_sanitizer_method(method_name.to_sym, &method_proc)
    end

    def build_option(method_name, method_proc)
      Universe.add_optional_method(method_name.to_sym, method_proc)
    end

    def new_particle(element, store, render, &_method_proc)
      Atome.define_method element do |params = nil, &user_proc|
        @history[element] ||= []
        if (params || params == false) && write_auth(element)
          params = particle_sanitizer(element, params, &user_proc)
          # the line below execute the main code when creating a new particle
          # ex : new({ particle: :my_particle } do....
          # instance_exec(params, user_proc, &method_proc) if method_proc.is_a?(Proc)
          Genesis.create_particle(element, store, render)
          # TODO: try to optimise and find a better way wo we can remove the condition below
          if @type == :group && !%i[type id collect layout].include?(element)
            collect.each do |collected_found|
              grab(collected_found).send(element, params, &user_proc)
            end
          end

          computed_params= send("set_#{element}", params, &user_proc) # sent to : create_particle / Atome.define_method "set_#{element}"

          # we historicize all write action below
          # we add the changes to the stack that must be synchronised
          Universe.historicize(@id, :write, element, params)
          computed_params
        elsif params || params == false
          "send a valid password to write #{element} value"
        elsif read_auth(element)
          # particle getter below
          value_found = instance_variable_get("@#{element}")
          # uncomment below to  historicize all read action
          # Universe.historicize(@id, :read, element, value_found)
          # we add the optional read plugin
          value_found = particle_read(element, value_found, &user_proc)
          value_found
          # TODO : create a fast method to get particle: eg:
          #  Atome.define_method "set_#{element}" ... =>  send("set_#{element}"
        else
          "send a valid password to read #{element} value"
        end

      end
    end

    def additional_particle_methods(element, store, rendering, &method_proc)
      # it allow the creation of method like top=, rotate=, ...
      Atome.define_method "#{element}=" do |params = nil, &user_proc|
        instance_exec(params, user_proc, &method_proc) if method_proc.is_a?(Proc)
        params = particle_sanitizer(element, params)
        particle_creation(element, params, store, rendering, &user_proc)
      end
    end

    def new_atome(element, &method_proc)

      # the method define below is the slowest but params are analysed and sanitized
      Atome.define_method element do |params = nil, &user_proc|
        instance_exec(params, user_proc, &method_proc) if method_proc.is_a?(Proc)
        if params
          params = atome_sanitizer(element, params, &user_proc)
          atome_processor(element, params, &user_proc)
        else
          # when no params passed whe assume teh user want a getter,
          # as getter should give us all atome of a given within the atome
          # ex : puts a.shape => return all atome with the type 'shape' in this atome
          collected_atomes = []
          # attached.each do |attached_atome|
          #   collected_atomes << attached_atome if grab(attached_atome).type.to_sym == element.to_sym
          # end
          # TODO : add category for atome( material/physical vs modifier : color, shadow, .. vs shape, image ..)
          # then add condition same things fo code in presets/atome atome_common
          if %i[color shadow paint].include?(element)
            # we do the same for apply to be able to retrieve 'color' and other atome that apply instead of being attached
            apply.each do |attached_atome|
              collected_atomes << attached_atome if grab(attached_atome).type.to_sym == element.to_sym
            end
          else
            # collected_atomes = attached
            attached.each do |attached_atome|
              collected_atomes << attached_atome if grab(attached_atome).type.to_sym == element.to_sym
            end
          end
          group({ collect: collected_atomes })

        end
      end

      # the method define below is the fastest params are passed directly
      Atome.define_method "set_#{element}" do |params, &user_proc|
        # we generate the corresponding module here:
        # Object.const_set(element, Module.new)
        # we add the newly created atome to the list of "child in it's category, eg if it's a shape we add the new atome
        # to the shape particles list : @!atome[:shape] << params[:id]
        Atome.new(params, &user_proc)
        # Now we return the newly created atome instead of the current atome that is the parent cf: b=box; c=b.circle
      end

    end

  end

end