lib/attio/resources/entry.rb



# frozen_string_literal: true

require_relative "../api_resource"

module Attio
  # Represents an entry in an Attio list
  # Entries link records to lists with custom attribute values
  class Entry < APIResource
    attr_reader :parent_record_id, :parent_object, :list_id
    attr_accessor :entry_values

    def initialize(attributes = {}, opts = {})
      super

      normalized_attrs = normalize_attributes(attributes)

      # Extract specific entry attributes
      @parent_record_id = normalized_attrs[:parent_record_id]
      @parent_object = normalized_attrs[:parent_object]
      @entry_values = normalized_attrs[:entry_values] || {}

      # Extract list_id from nested ID structure
      if normalized_attrs[:id].is_a?(Hash)
        @list_id = normalized_attrs[:id][:list_id]
      end
    end

    class << self
      # API endpoint path for entries (nested under lists)
      # @return [String] The API path
      def resource_path
        "lists"
      end

      # List entries for a list
      def list(list: nil, **params)
        validate_list_identifier!(list)

        response = execute_request(:POST, "#{resource_path}/#{list}/entries/query", params, {})
        APIResource::ListObject.new(response, self, params.merge(list: list), params)
      end
      alias_method :all, :list

      # Create a new entry
      def create(list: nil, parent_record_id: nil, parent_object: nil, entry_values: nil, **opts)
        validate_list_identifier!(list)
        validate_parent_params!(parent_record_id, parent_object)

        request_params = {
          data: {
            parent_record_id: parent_record_id,
            parent_object: parent_object,
            entry_values: entry_values || {}
          }
        }

        response = execute_request(:POST, "#{resource_path}/#{list}/entries", request_params, opts)
        new(response["data"] || response, opts)
      end

      # Retrieve a specific entry
      def retrieve(list: nil, entry_id: nil, **opts)
        validate_list_identifier!(list)
        validate_entry_id!(entry_id)

        response = execute_request(:GET, "#{resource_path}/#{list}/entries/#{entry_id}", {}, opts)
        new(response["data"] || response, opts)
      end
      alias_method :get, :retrieve
      alias_method :find, :retrieve

      # Update an entry
      def update(list: nil, entry_id: nil, entry_values: nil, mode: nil, **opts)
        validate_list_identifier!(list)
        validate_entry_id!(entry_id)

        request_params = {
          data: {
            entry_values: entry_values || {}
          }
        }

        # Add mode parameter for append operations
        if mode == "append"
          request_params[:mode] = "append"
        end

        response = execute_request(:PATCH, "#{resource_path}/#{list}/entries/#{entry_id}", request_params, opts)
        new(response["data"] || response, opts)
      end

      # Delete an entry
      def delete(list: nil, entry_id: nil, **opts)
        validate_list_identifier!(list)
        validate_entry_id!(entry_id)

        execute_request(:DELETE, "#{resource_path}/#{list}/entries/#{entry_id}", {}, opts)
        true
      end
      alias_method :destroy, :delete

      # Assert an entry by parent record
      def assert_by_parent(list: nil, parent_record_id: nil, parent_object: nil, entry_values: nil, **opts)
        validate_list_identifier!(list)
        validate_parent_params!(parent_record_id, parent_object)

        request_params = {
          data: {
            parent_record_id: parent_record_id,
            parent_object: parent_object,
            entry_values: entry_values || {}
          }
        }

        response = execute_request(:PUT, "#{resource_path}/#{list}/entries", request_params, opts)
        new(response["data"] || response, opts)
      end

      # List attribute values for an entry
      def list_attribute_values(list: nil, entry_id: nil, attribute_id: nil, **opts)
        validate_list_identifier!(list)
        validate_entry_id!(entry_id)
        raise ArgumentError, "Attribute ID is required" if attribute_id.nil? || attribute_id.to_s.empty?

        response = execute_request(:GET, "#{resource_path}/#{list}/entries/#{entry_id}/attributes/#{attribute_id}/values", {}, opts)
        response["data"] || []
      end

      private

      def validate_list_identifier!(list)
        raise ArgumentError, "List identifier is required" if list.nil? || list.to_s.empty?
      end

      def validate_entry_id!(entry_id)
        raise ArgumentError, "Entry ID is required" if entry_id.nil? || entry_id.to_s.empty?
      end

      def validate_parent_params!(parent_record_id, parent_object)
        if parent_record_id.nil? || parent_object.nil?
          raise ArgumentError, "parent_record_id and parent_object are required"
        end
      end
    end

    # Instance methods

    def save(**opts)
      raise InvalidRequestError, "Cannot save an entry without an ID" unless persisted?
      raise InvalidRequestError, "Cannot save without list context" unless list_id

      # For Entry, we always save the full entry_values
      params = {
        data: {
          entry_values: entry_values
        }
      }

      response = self.class.send(:execute_request, :PATCH, resource_path, params, opts)
      update_from(response[:data] || response)
      reset_changes!
      self
    end

    def destroy(**opts)
      raise InvalidRequestError, "Cannot destroy an entry without an ID" unless persisted?
      raise InvalidRequestError, "Cannot destroy without list context" unless list_id

      entry_id = extract_entry_id
      self.class.send(:execute_request, :DELETE, "lists/#{list_id}/entries/#{entry_id}", {}, opts)
      @attributes.clear
      @changed_attributes.clear
      @id = nil
      true
    end

    def resource_path
      raise InvalidRequestError, "Cannot generate path without list context" unless list_id
      entry_id = extract_entry_id
      "lists/#{list_id}/entries/#{entry_id}"
    end

    private

    def extract_entry_id
      case id
      when Hash
        id[:entry_id] || id["entry_id"]
      else
        id
      end
    end

    def to_h
      {
        id: id,
        parent_record_id: parent_record_id,
        parent_object: parent_object,
        created_at: created_at&.iso8601,
        entry_values: entry_values
      }.compact
    end

    def inspect
      "#<#{self.class.name}:#{object_id} id=#{id.inspect} parent=#{parent_object}##{parent_record_id} values=#{entry_values.inspect}>"
    end
  end
end