defmodule Raxol.Terminal.Sync.Protocol do @moduledoc """ Defines the synchronization protocol for terminal components. Handles message formats, versioning, and conflict resolution strategies. """ # Types @type sync_message :: %{ type: :sync | :ack | :conflict | :resolve, component_id: String.t(), component_type: :split | :window | :tab, state: term(), metadata: %{ version: non_neg_integer(), timestamp: non_neg_integer(), source: String.t(), consistency: :strong | :eventual | :causal } } @type sync_result :: :ok | {:error, :conflict | :version_mismatch | :invalid_state} # Message Types @sync_type :sync @ack_type :ack @conflict_type :conflict @resolve_type :resolve # Protocol Functions def create_sync_message(component_id, component_type, state, opts \\ []) do # Convert keyword list to map if needed opts_map = convert_opts_to_map(Keyword.keyword?(opts), opts) %{ type: @sync_type, component_id: component_id, component_type: component_type, state: state, metadata: %{ version: Map.get(opts_map, :version, System.monotonic_time()), timestamp: System.system_time(), source: Map.get(opts_map, :source, "unknown"), consistency: Map.get( opts_map, :consistency, get_default_consistency(component_type) ) } } end def create_ack_message(component_id, component_type, version) do %{ type: @ack_type, component_id: component_id, component_type: component_type, metadata: %{ version: version, timestamp: System.system_time() } } end def create_conflict_message( component_id, component_type, current_state, incoming_state ) do %{ type: @conflict_type, component_id: component_id, component_type: component_type, states: %{ current: current_state, incoming: incoming_state }, metadata: %{ timestamp: System.system_time() } } end def create_resolve_message( component_id, component_type, resolved_state, version ) do %{ type: @resolve_type, component_id: component_id, component_type: component_type, state: resolved_state, metadata: %{ version: version, timestamp: System.system_time() } } end # Protocol Handlers def handle_sync_message(message, current_state) do case validate_message(message) do :ok -> handle_valid_sync(message, current_state) {:error, reason} -> {:error, reason} end end def handle_ack_message(message, current_state) do case validate_message(message) do :ok -> handle_valid_ack(message, current_state) {:error, reason} -> {:error, reason} end end def handle_conflict_message(message, current_state) do case validate_message(message) do :ok -> handle_valid_conflict(message, current_state) {:error, reason} -> {:error, reason} end end def handle_resolve_message(message, current_state) do case validate_message(message) do :ok -> handle_valid_resolve(message, current_state) {:error, reason} -> {:error, reason} end end # Private Functions defp validate_message(message) when not is_map(message) do {:error, :invalid_message} end defp validate_message(message) do with true <- Map.has_key?(message, :type) || {:error, :missing_type}, true <- Map.has_key?(message, :component_id) || {:error, :missing_component_id}, true <- Map.has_key?(message, :component_type) || {:error, :missing_component_type}, true <- Map.has_key?(message, :metadata) || {:error, :missing_metadata} do :ok else {:error, reason} -> {:error, reason} false -> {:error, :unknown_validation_error} end end defp handle_valid_sync(message, current_state) do case resolve_conflict(message, current_state) do :accept -> {:ok, message.state, message.metadata.version} :reject -> {:error, :version_mismatch} :conflict -> {:error, :conflict} end end defp handle_valid_ack(message, current_state) do handle_ack_version_check(message.metadata.version == current_state.metadata.version) end defp handle_valid_conflict(message, current_state) do # Compare incoming state with current state case resolve_conflict(message.states.incoming, current_state) do :accept -> {:ok, message.states.incoming} :reject -> {:ok, current_state} :conflict -> {:error, :unresolved_conflict} end end defp handle_valid_resolve(message, current_state) do handle_resolve_version_check( message.metadata.version > current_state.metadata.version, message.state ) end defp resolve_conflict(message, current_state) do # Extract metadata from both states, handling different formats message_metadata = extract_metadata(message) current_metadata = extract_metadata(current_state) case {message_metadata.consistency, current_metadata.consistency} do {:strong, :strong} -> resolve_strong_consistency(message_metadata.version > current_metadata.version) {:strong, _} -> :accept {_, :strong} -> :reject _ -> # Both eventual consistency resolve_eventual_consistency( message_metadata.version, current_metadata.version ) end end defp extract_metadata(%{metadata: meta}) do %{ version: Map.get(meta, :version, 0), consistency: Map.get(meta, :consistency, :eventual) } end defp extract_metadata(%{version: version} = state) do %{ version: version, consistency: Map.get(state, :consistency, :eventual) } end defp extract_metadata(_state) do %{ version: 0, consistency: :eventual } end defp get_default_consistency(:split), do: :strong defp get_default_consistency(:window), do: :strong defp get_default_consistency(:tab), do: :eventual defp get_default_consistency(_), do: :eventual # Helper functions for pattern matching refactoring defp convert_opts_to_map(true, opts), do: Map.new(opts) defp convert_opts_to_map(false, opts), do: opts defp handle_ack_version_check(true), do: :ok defp handle_ack_version_check(false), do: {:error, :version_mismatch} defp handle_resolve_version_check(true, state), do: {:ok, state} defp handle_resolve_version_check(false, _state), do: {:error, :version_mismatch} defp resolve_strong_consistency(true), do: :accept defp resolve_strong_consistency(false), do: :reject defp resolve_eventual_consistency(message_version, current_version) when message_version > current_version do :accept end defp resolve_eventual_consistency(message_version, current_version) when message_version == current_version do :conflict end defp resolve_eventual_consistency(_message_version, _current_version) do :reject end end