defmodule SnmpKit.SnmpLib.MIB.AST do
  @moduledoc """
  Abstract Syntax Tree definitions for MIB compilation.

  Faithfully ported from Erlang OTP snmpc_mib_gram.yrl and related structures.
  Provides the complete AST representation needed for SNMP MIB compilation.
  """

  @type line_number :: integer()

  # Top-level MIB structure
  @type mib :: %{
          __type__: :mib,
          name: binary(),
          last_updated: binary() | nil,
          organization: binary() | nil,
          contact_info: binary() | nil,
          description: binary() | nil,
          revision_history: [revision()],
          imports: [import()],
          definitions: [definition()],
          oid_tree: oid_tree(),
          metadata: metadata()
        }

  @type revision :: %{
          __type__: :revision,
          date: binary(),
          description: binary(),
          line: line_number()
        }

  # Import statement from other MIBs
  @type import :: %{
          __type__: :import,
          symbols: [binary()],
          from_module: binary(),
          line: line_number()
        }

  # MIB definitions (various types)
  @type definition ::
          object_type()
          | object_identity()
          | object_group()
          | notification_type()
          | notification_group()
          | module_identity()
          | module_compliance()
          | agent_capabilities()
          | textual_convention()
          | trap_type()
          | object_identifier_assignment()

  # Object Type Definition (OBJECT-TYPE)
  @type object_type :: %{
          __type__: :object_type,
          name: binary(),
          syntax: syntax(),
          units: binary() | nil,
          max_access: access_level(),
          status: status(),
          description: binary(),
          reference: binary() | nil,
          index: index_spec() | nil,
          augments: binary() | nil,
          defval: default_value() | nil,
          oid: oid(),
          line: line_number()
        }

  # Object Identity Definition (OBJECT-IDENTITY)
  @type object_identity :: %{
          __type__: :object_identity,
          name: binary(),
          status: status(),
          description: binary(),
          reference: binary() | nil,
          oid: oid(),
          line: line_number()
        }

  # Module Identity Definition (MODULE-IDENTITY) - SNMPv2 only
  @type module_identity :: %{
          __type__: :module_identity,
          name: binary(),
          last_updated: binary(),
          organization: binary(),
          contact_info: binary(),
          description: binary(),
          revision_history: [revision()],
          oid: oid(),
          line: line_number()
        }

  # Object Group Definition (OBJECT-GROUP)
  @type object_group :: %{
          __type__: :object_group,
          name: binary(),
          objects: [binary()],
          status: status(),
          description: binary(),
          reference: binary() | nil,
          oid: oid(),
          line: line_number()
        }

  # Notification Type Definition (NOTIFICATION-TYPE)
  @type notification_type :: %{
          __type__: :notification_type,
          name: binary(),
          objects: [binary()],
          status: status(),
          description: binary(),
          reference: binary() | nil,
          oid: oid(),
          line: line_number()
        }

  # Notification Group Definition (NOTIFICATION-GROUP)
  @type notification_group :: %{
          __type__: :notification_group,
          name: binary(),
          notifications: [binary()],
          status: status(),
          description: binary(),
          reference: binary() | nil,
          oid: oid(),
          line: line_number()
        }

  # Module Compliance Definition (MODULE-COMPLIANCE)
  @type module_compliance :: %{
          __type__: :module_compliance,
          name: binary(),
          status: status(),
          description: binary(),
          reference: binary() | nil,
          modules: [compliance_module()],
          oid: oid(),
          line: line_number()
        }

  @type compliance_module :: %{
          module_name: binary(),
          mandatory_groups: [binary()],
          compliance_objects: [compliance_object()]
        }

  @type compliance_object :: %{
          object: binary(),
          syntax: syntax() | nil,
          write_syntax: syntax() | nil,
          access: access_level() | nil,
          description: binary() | nil
        }

  # Agent Capabilities Definition (AGENT-CAPABILITIES)
  @type agent_capabilities :: %{
          __type__: :agent_capabilities,
          name: binary(),
          product_release: binary(),
          status: status(),
          description: binary(),
          reference: binary() | nil,
          modules: [capability_module()],
          oid: oid(),
          line: line_number()
        }

  @type capability_module :: %{
          module_name: binary(),
          includes: [binary()],
          variations: [object_variation()]
        }

  @type object_variation :: %{
          object: binary(),
          syntax: syntax() | nil,
          write_syntax: syntax() | nil,
          access: access_level() | nil,
          creation: boolean(),
          defval: default_value() | nil,
          description: binary()
        }

  # Textual Convention Definition (TEXTUAL-CONVENTION)
  @type textual_convention :: %{
          __type__: :textual_convention,
          name: binary(),
          display_hint: binary() | nil,
          status: status(),
          description: binary(),
          reference: binary() | nil,
          syntax: syntax(),
          line: line_number()
        }

  # Trap Type Definition (TRAP-TYPE) - SNMPv1 only
  @type trap_type :: %{
          __type__: :trap_type,
          name: binary(),
          enterprise: oid(),
          variables: [binary()],
          description: binary() | nil,
          reference: binary() | nil,
          trap_number: integer(),
          line: line_number()
        }

  # Object Identifier Assignment
  @type object_identifier_assignment :: %{
          __type__: :object_identifier_assignment,
          name: binary(),
          oid: oid(),
          line: line_number()
        }

  # Syntax Definitions
  @type syntax ::
          primitive_syntax() | constructed_syntax() | named_syntax()

  @type primitive_syntax ::
          :integer
          | :octet_string
          | :object_identifier
          | :null
          | :real
          | {:integer, constraints()}
          | {:octet_string, constraints()}
          | {:object_identifier, constraints()}

  @type constructed_syntax ::
          {:sequence, [sequence_element()]}
          | {:sequence_of, syntax()}
          | {:choice, [choice_element()]}
          | {:bit_string, [named_bit()]}

  @type named_syntax ::
          {:named_type, binary()}
          | {:application_type, tag(), syntax()}
          | {:context_type, tag(), syntax()}

  @type sequence_element :: %{
          name: binary(),
          syntax: syntax(),
          optional: boolean(),
          default: default_value() | nil
        }

  @type choice_element :: %{
          name: binary(),
          syntax: syntax()
        }

  @type named_bit :: %{
          name: binary(),
          bit_number: integer()
        }

  @type tag :: integer()

  # Constraint Definitions
  @type constraints :: [constraint()]

  @type constraint ::
          {:size, size_constraint()}
          | {:range, range_constraint()}
          | {:named_values, [named_value()]}
          | {:contained_subtype, syntax()}

  @type size_constraint ::
          integer() | {integer(), integer()} | [size_range()]

  @type range_constraint ::
          {integer(), integer()} | [range_spec()]

  @type size_range :: integer() | {integer(), integer()}
  @type range_spec :: integer() | {integer(), integer()}

  @type named_value :: %{
          name: binary(),
          value: integer()
        }

  # Index Specifications
  @type index_spec ::
          {:index, [index_element()]}
          | {:implied, [index_element()]}

  @type index_element ::
          binary() | {:implied, binary()}

  # Access Levels
  @type access_level ::
          :not_accessible
          | :accessible_for_notify
          | :read_only
          | :read_write
          | :read_create
          # Legacy SNMPv1 access levels
          | :write_only

  # Status Values
  @type status :: :current | :deprecated | :obsolete | :mandatory

  # Default Values
  @type default_value ::
          nil
          | integer()
          | binary()
          | atom()
          | [default_value()]
          | {:named_value, binary()}
          | {:bit_string, [binary()]}

  # OID Representation
  @type oid :: [integer()] | [oid_element()]
  @type oid_element :: integer() | {binary(), integer()}

  # Performance: Use ETS for large OID trees in production
  @type oid_tree :: :ets.tid() | map()

  # Compilation Metadata
  @type metadata :: %{
          compile_time: DateTime.t(),
          compiler_version: binary(),
          source_file: Path.t(),
          snmp_version: :v1 | :v2c,
          dependencies: [binary()],
          warnings: [binary()],
          line_count: integer()
        }

  # Helper Functions for AST Construction

  @doc """
  Create a new MIB AST node.
  """
  @spec new_mib(binary(), keyword()) :: mib()
  def new_mib(name, opts \\ []) do
    %{
      __type__: :mib,
      name: name,
      last_updated: opts[:last_updated],
      organization: opts[:organization],
      contact_info: opts[:contact_info],
      description: opts[:description],
      revision_history: opts[:revision_history] || [],
      imports: opts[:imports] || [],
      definitions: opts[:definitions] || [],
      oid_tree: opts[:oid_tree] || %{},
      metadata: opts[:metadata] || %{}
    }
  end

  @doc """
  Create a new object type definition.
  """
  @spec new_object_type(binary(), keyword()) :: object_type()
  def new_object_type(name, opts) do
    %{
      __type__: :object_type,
      name: name,
      syntax: opts[:syntax],
      units: opts[:units],
      max_access: opts[:max_access],
      status: opts[:status],
      description: opts[:description],
      reference: opts[:reference],
      index: opts[:index],
      augments: opts[:augments],
      defval: opts[:defval],
      oid: opts[:oid],
      line: opts[:line]
    }
  end

  @doc """
  Create a new object identity definition.
  """
  @spec new_object_identity(binary(), keyword()) :: object_identity()
  def new_object_identity(name, opts) do
    %{
      __type__: :object_identity,
      name: name,
      status: opts[:status],
      description: opts[:description],
      reference: opts[:reference],
      oid: opts[:oid],
      line: opts[:line]
    }
  end

  @doc """
  Create a new import statement.
  """
  @spec new_import([binary()], binary(), line_number()) :: import()
  def new_import(symbols, from_module, line) do
    %{
      __type__: :import,
      symbols: symbols,
      from_module: from_module,
      line: line
    }
  end

  @doc """
  Determine SNMP version based on MIB content.

  Per Erlang implementation: presence of MODULE-IDENTITY indicates SNMPv2.
  """
  @spec determine_snmp_version([definition()]) :: :v1 | :v2c
  def determine_snmp_version(definitions) do
    has_module_identity =
      Enum.any?(definitions, fn
        %{__type__: :module_identity} -> true
        _ -> false
      end)

    if has_module_identity, do: :v2c, else: :v1
  end

  @doc """
  Build an OID tree from definitions for fast lookups.
  """
  @spec build_oid_tree([definition()]) :: :ets.tid()
  def build_oid_tree(definitions) do
    # Use ETS for performance with large MIBs
    tid = :ets.new(:oid_tree, [:set, :protected])

    Enum.each(definitions, fn definition ->
      case extract_oid_mapping(definition) do
        {name, oid} when is_list(oid) ->
          :ets.insert(tid, {oid, name})
          :ets.insert(tid, {name, oid})

        nil ->
          :ok
      end
    end)

    tid
  end

  # Extract OID mapping from definition
  defp extract_oid_mapping(%{name: name, oid: oid}) when is_list(oid) do
    {name, oid}
  end

  defp extract_oid_mapping(_), do: nil

  @doc """
  Validate AST node structure.
  """
  @spec validate_node(term()) :: {:ok, term()} | {:error, binary()}
  def validate_node(%{__type__: type} = node)
      when type in [
             :mib,
             :object_type,
             :object_identity,
             :module_identity,
             :object_group,
             :notification_type,
             :notification_group,
             :module_compliance,
             :agent_capabilities,
             :textual_convention,
             :trap_type,
             :object_identifier_assignment,
             :import,
             :revision
           ] do
    {:ok, node}
  end

  def validate_node(node) do
    {:error, "Invalid AST node: #{inspect(node)}"}
  end

  @doc """
  Pretty print AST node for debugging.
  """
  @spec pretty_print(term()) :: binary()
  def pretty_print(%{__type__: type, name: name}) do
    "#{type}: #{name}"
  end

  def pretty_print(%{__type__: type}) do
    "#{type}"
  end

  def pretty_print(other) do
    inspect(other)
  end
end
