# This module declare all the methods and macro related to deserializing json in `Clear::Model`
module Clear::Model::JSONDeserialize
  macro included
    macro included # When included into Model
      macro inherited #Polymorphism
        macro finished
          columns_to_instance_vars
        end
      end

      macro finished
        columns_to_instance_vars
      end
    end
  end
end

# Used internally to deserialise json
macro columns_to_instance_vars
  # :nodoc:
  struct Assigner
    include JSON::Serializable

    {% for name, settings in COLUMNS %}
      @[JSON::Field(presence: true)]
      getter {{name.id}} : {{settings[:type]}} {% unless settings[:type].resolve.nilable? %} | Nil {% end %}
      @[JSON::Field(ignore: true)]
      getter? {{name.id}}_present : Bool
    {% end %}

    # Create a new empty model and fill the columns with object's instance variables
    # Trusted flag set to true will allow mass assignment without protection
    def create(trusted : Bool)
      assign_columns({{@type}}.new, trusted)
    end

    # Update the inputted model and assign the columns with object's instance variables
    # Trusted flag set to true will allow mass assignment without protection
    def update(model, trusted : Bool)
      assign_columns(model, trusted)
    end

    macro finished
      # Assign properties to the model inputted with object's instance variables
      # Trusted flag set to true will allow mass assignment without protection
      protected def assign_columns(model, trusted : Bool)
        {% for name, settings in COLUMNS %}
          if ({{ settings[:mass_assign] }} || trusted) && self.{{name.id}}_present?
            %value = self.{{name.id}}
            {% if settings[:type].resolve.nilable? %}
              model.{{name.id}} = %value
            {% else %}
              model.{{name.id}} = %value unless %value.nil?
            {% end %}
          end
        {% end %}

        model
      end
    end
  end

  # Create a new empty model and fill the columns from json. Returns the new model
  #
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def self.from_json(string_or_io : String | IO, trusted : Bool = false)
    Assigner.from_json(string_or_io).create(trusted)
  end

  # Create a new model from json and save it. Returns the model.
  #
  # The model may not be saved due to validation failure;
  # check the returned model `errors?` and `persisted?` flags.
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def self.create_from_json(string_or_io : String | IO, trusted : Bool = false)
    mdl = self.from_json(string_or_io, trusted)
    mdl.save
    mdl
  end

  # Create a new model from json and save it. Returns the model.
  #
  # Returns the newly inserted model
  # Raises an exception if validation failed during the saving process.
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def self.create_from_json!(string_or_io : String | IO, trusted : Bool = false)
    self.from_json(string_or_io, trusted).save!
  end

  # Set the fields from json passed as argument
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def set_from_json(string_or_io : String | IO, trusted : Bool = false)
    Assigner.from_json(string_or_io).update(self, trusted)
  end

  # Set the fields from json passed as argument and call `save` on the object
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def update_from_json(string_or_io : String | IO, trusted : Bool = false)
    mdl = set_from_json(string_or_io, trusted)
    mdl.save
    mdl
  end

  # Set the fields from json passed as argument and call `save!` on the object
  # Trusted flag set to true will allow mass assignment without protection, FALSE by default
  def update_from_json!(string_or_io : String | IO, trusted : Bool = false)
    set_from_json(string_or_io, trusted).save!
  end
end
