module Netzke
  module Form
    # Ext.form.Panel-based component with the following features
    #
    # * automatic default attribute configuration (overridable via config)
    # * model validations
    # * dynamic loading of form data
    # * lockable mode
    #
    # Client-side methods are documented here: http://api.netzke.org/client/classes/Netzke.Form.Base.html.
    #
    # == Configuration
    #
    # [model]
    #
    #   Name of the ActiveRecord model that provides data to this Grid (e.g. "User") or the model's class (e.g. User).
    #   Model is optional if you provide custom implementation of the endpoints.
    #
    # [record_id]
    #
    # Id of the record to be displayd in the form. Also see +:record+
    #
    # [record]
    #
    #   Record to be displayd in the form. Takes precedence over +:record_id+
    #
    # [items]
    #
    #   The layout of the fields as an array. See "Layout configuration".
    #
    # [mode]
    #
    #   Render mode, accepted options:
    #
    #   * +lockable+ - makes the form panel load initially in "display mode", then lets "unlock" it, change the values, and "lock" it again, while updating the values on the server
    #
    # == Configuring attributes
    #
    # === Overriding individual attributes
    # Use the +attribute+ DSL method to override configuration for a specific attribute. See +Basepack::Attributes+.
    #
    # === Layout configuration
    #
    # The layout of the form is configured by supplying the +item+ config option, same way it would be configured in Ext (thus allowing for complex form layouts). Form will expand fields by looking at their names (unless +bind+ is set to +false+ for a specific field).
    #
    # == Endpoints
    # Form implements the following endpoints:
    #
    # * +load+ - loads a record with a given id from the server, e.g.:
    #
    #     someForm.server.load({id: 100});
    #
    # * +submit+ - gets called when the form gets submitted (e.g. by pressing the Apply button, or by calling netzkeOnApply)
    # * +get_combobox_options+ - gets called when a 'remote' combobox field gets expanded

    autoload :Endpoints, 'netzke/form/endpoints'
    autoload :Services, 'netzke/form/services'

    class Base < Netzke::Base
      include Netzke::Form::Endpoints
      include Netzke::Form::Services
      include Netzke::Basepack::Attributes
      include Netzke::Basepack::Fields
      include Netzke::Basepack::DataAccessor

      client_class do |c|
        c.extend = "Ext.form.Panel"
        c.require :readonly_mode
      end

      def configure_client(c)
        super

        configure_locked(c)

        if model_adapter
          c.pri = model_adapter.primary_key
        end

        if !c.multiedit
          c.record = js_record_data if record
        else
          c.record_id = c.record = nil if c.multiedit # never set record_id in multi-edit mode
        end
      end

      action :apply do |a|
        a.icon = :tick
      end

      action :edit do |a|
        a.icon = :pencil
      end

      action :cancel do |a|
        a.icon = :cancel
      end

      def configure_locked(c)
        c[:locked] = c[:locked].nil? ? (c[:mode] == :lockable) : c[:locked]
      end

      # Extra JavaScripts and stylesheets
      client_styles do |c|
        c.require :readonly_mode
      end

      # A hash of record data including the meta field
      def js_record_data
        model_adapter.record_to_hash(record, fields.values).merge(:meta => meta_field).netzke_literalize_keys
      end

      def record
        @record ||= config[:record] || config[:record_id] && model_adapter.find_record(config[:record_id])
      end

      def bbar
        config.has_key?(:bbar) ? config[:bbar] : default_bbar
      end

      def default_bbar
        [].tap do |bbar|
          unless config.read_only
            bbar << "->" << :apply
          end
        end
      end

      def multiedit?
        !!config.multiedit
      end

      private

      def validate_config(c)
        c.bbar = bbar
        super
      end

      def normalize_config
        config.items = items
        @fields_from_items = {} # will be built during execution of `super`
        super
      end

      def self.server_side_config_options
        super + [:scope]
      end

      def meta_field
        {}.tap do |res|
          assoc_values = get_association_values
          res[:association_values] = assoc_values.netzke_literalize_keys if record && !assoc_values.empty?
        end
      end

      def get_association_values
        fields_that_need_associated_values = fields.select{ |k,v| k.to_s.index("__") && !fields[k][:nested_attribute] }
        # Take care of Ruby 1.8.7
        if fields_that_need_associated_values.is_a?(Array)
          fields_that_need_associated_values = fields_that_need_associated_values.inject({}){|r,(k,v)| r.merge(k => v)}
        end

        fields_that_need_associated_values.each_pair.inject({}) do |r,(k,v)|
          r.merge(k => model_adapter.record_value_for_attribute(record, fields_that_need_associated_values[k], true))
        end
      end
    end
  end
end
