-module(jchat_methods). -export([handle_method/3]). -include("jchat.hrl"). %% Handle JMAP method calls handle_method(<<"Core/echo">>, Args, _AccountId) -> {ok, Args}; %% Conversation methods handle_method(<<"Conversation/get">>, Args, AccountId) -> handle_conversation_get(Args, AccountId); handle_method(<<"Conversation/set">>, Args, AccountId) -> handle_conversation_set(Args, AccountId); handle_method(<<"Conversation/changes">>, Args, AccountId) -> handle_conversation_changes(Args, AccountId); handle_method(<<"Conversation/query">>, Args, AccountId) -> handle_conversation_query(Args, AccountId); %% Message methods handle_method(<<"Message/get">>, Args, AccountId) -> handle_message_get(Args, AccountId); handle_method(<<"Message/set">>, Args, AccountId) -> handle_message_set(Args, AccountId); handle_method(<<"Message/changes">>, Args, AccountId) -> handle_message_changes(Args, AccountId); handle_method(<<"Message/query">>, Args, AccountId) -> handle_message_query(Args, AccountId); %% Participant methods handle_method(<<"Participant/get">>, Args, AccountId) -> handle_participant_get(Args, AccountId); handle_method(<<"Participant/set">>, Args, AccountId) -> handle_participant_set(Args, AccountId); %% Presence methods handle_method(<<"Presence/get">>, Args, AccountId) -> handle_presence_get(Args, AccountId); handle_method(<<"Presence/set">>, Args, AccountId) -> handle_presence_set(Args, AccountId); handle_method(Method, _Args, _AccountId) -> {error, #{type => <<"unknownMethod">>, description => <<"Unknown method: ", Method/binary>>}}. %% Conversation/get implementation handle_conversation_get(#{<<"accountId">> := AccountId} = Args, AccountId) -> Ids = maps:get(<<"ids">>, Args, null), Properties = maps:get(<<"properties">>, Args, null), case get_conversations(Ids) of {ok, Conversations} -> List = [conversation_to_jmap(Conv, Properties) || Conv <- Conversations], {ok, #{ <<"accountId">> => AccountId, <<"state">> => get_state(conversation), <<"list">> => List, <<"notFound">> => [] }}; {error, Error} -> {error, Error} end; handle_conversation_get(_, _) -> {error, account_not_found}. %% Conversation/set implementation handle_conversation_set(#{<<"accountId">> := AccountId} = Args, AccountId) -> Create = maps:get(<<"create">>, Args, #{}), Update = maps:get(<<"update">>, Args, #{}), Destroy = maps:get(<<"destroy">>, Args, []), {CreatedMap, NotCreated} = handle_conversation_creates(Create), {UpdatedList, NotUpdated} = handle_conversation_updates(Update), {DestroyedList, NotDestroyed} = handle_conversation_destroys(Destroy), {ok, #{ <<"accountId">> => AccountId, <<"oldState">> => get_state(conversation), <<"newState">> => get_state(conversation), <<"created">> => CreatedMap, <<"updated">> => UpdatedList, <<"destroyed">> => DestroyedList, <<"notCreated">> => NotCreated, <<"notUpdated">> => NotUpdated, <<"notDestroyed">> => NotDestroyed }}; handle_conversation_set(_, _) -> {error, account_not_found}. %% Message/get implementation handle_message_get(#{<<"accountId">> := AccountId} = Args, AccountId) -> Ids = maps:get(<<"ids">>, Args, null), Properties = maps:get(<<"properties">>, Args, null), case get_messages(Ids) of {ok, Messages} -> List = [message_to_jmap(Msg, Properties) || Msg <- Messages], {ok, #{ <<"accountId">> => AccountId, <<"state">> => get_state(message), <<"list">> => List, <<"notFound">> => [] }}; {error, Error} -> {error, Error} end; handle_message_get(_, _) -> {error, account_not_found}. %% Message/set implementation handle_message_set(#{<<"accountId">> := AccountId} = Args, AccountId) -> Create = maps:get(<<"create">>, Args, #{}), Update = maps:get(<<"update">>, Args, #{}), Destroy = maps:get(<<"destroy">>, Args, []), {CreatedMap, NotCreated} = handle_message_creates(Create), {UpdatedList, NotUpdated} = handle_message_updates(Update), {DestroyedList, NotDestroyed} = handle_message_destroys(Destroy), {ok, #{ <<"accountId">> => AccountId, <<"oldState">> => get_state(message), <<"newState">> => get_state(message), <<"created">> => CreatedMap, <<"updated">> => UpdatedList, <<"destroyed">> => DestroyedList, <<"notCreated">> => NotCreated, <<"notUpdated">> => NotUpdated, <<"notDestroyed">> => NotDestroyed }}; handle_message_set(_, _) -> {error, account_not_found}. handle_conversation_changes(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"oldState">> => <<"0">>, <<"newState">> => get_state(conversation), <<"hasMoreChanges">> => false, <<"created">> => [], <<"updated">> => [], <<"destroyed">> => [] }}. handle_conversation_query(Args, AccountId) -> case jchat_db:query_conversations(AccountId, Args) of {ok, Conversations} -> Ids = [Conv#conversation.id || Conv <- Conversations], Total = length(Conversations), {ok, #{ <<"accountId">> => AccountId, <<"queryState">> => get_state(conversation), <<"canCalculateChanges">> => true, <<"position">> => 0, <<"ids">> => Ids, <<"total">> => Total }}; {error, Error} -> {error, Error} end. handle_message_changes(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"oldState">> => <<"0">>, <<"newState">> => get_state(message), <<"hasMoreChanges">> => false, <<"created">> => [], <<"updated">> => [], <<"destroyed">> => [] }}. handle_message_query(Args, AccountId) -> Filter = maps:get(<<"filter">>, Args, #{}), case jchat_db:query_messages(Filter, []) of {ok, Messages} -> Ids = [Msg#message.id || Msg <- Messages], Total = length(Messages), {ok, #{ <<"accountId">> => AccountId, <<"queryState">> => get_state(message), <<"canCalculateChanges">> => true, <<"position">> => 0, <<"ids">> => Ids, <<"total">> => Total }}; {error, Error} -> {error, Error} end. handle_participant_get(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"state">> => get_state(participant), <<"list">> => [], <<"notFound">> => [] }}. handle_participant_set(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"oldState">> => get_state(participant), <<"newState">> => get_state(participant), <<"created">> => #{}, <<"updated">> => [], <<"destroyed">> => [], <<"notCreated">> => #{}, <<"notUpdated">> => [], <<"notDestroyed">> => [] }}. handle_presence_get(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"state">> => get_state(presence), <<"list">> => [], <<"notFound">> => [] }}. handle_presence_set(_Args, _AccountId) -> {ok, #{ <<"accountId">> => <<"default">>, <<"oldState">> => get_state(presence), <<"newState">> => get_state(presence), <<"created">> => #{}, <<"updated">> => [], <<"destroyed">> => [], <<"notCreated">> => #{}, <<"notUpdated">> => [], <<"notDestroyed">> => [] }}. %% Helper functions get_conversations(null) -> % Return all conversations (simplified) {ok, []}; get_conversations(Ids) -> Results = [jchat_db:get_conversation(Id) || Id <- Ids], Conversations = [Conv || {ok, Conv} <- Results], {ok, Conversations}. get_messages(null) -> {ok, []}; get_messages(Ids) -> Results = [jchat_db:get_message(Id) || Id <- Ids], Messages = [Msg || {ok, Msg} <- Results], {ok, Messages}. handle_conversation_creates(Creates) -> maps:fold(fun(CreationId, ConvData, {CreatedAcc, NotCreatedAcc}) -> Id = jchat_utils:generate_id(), case jchat_db:create_conversation(Id, ConvData) of {ok, Conv} -> JMAPConv = conversation_to_jmap(Conv, null), {CreatedAcc#{CreationId => JMAPConv}, NotCreatedAcc}; {error, Error} -> {CreatedAcc, NotCreatedAcc#{CreationId => jchat_utils:format_error(Error)}} end end, {#{}, #{}}, Creates). handle_conversation_updates(Updates) -> maps:fold(fun(Id, UpdateData, {UpdatedAcc, NotUpdatedAcc}) -> case jchat_db:update_conversation(Id, UpdateData) of {ok, Conv} -> JMAPConv = conversation_to_jmap(Conv, null), {[JMAPConv | UpdatedAcc], NotUpdatedAcc}; {error, Error} -> {UpdatedAcc, NotUpdatedAcc#{Id => jchat_utils:format_error(Error)}} end end, {[], #{}}, Updates). handle_conversation_destroys(Destroy) -> % Simplified - would implement actual deletion {Destroy, #{}}. handle_message_creates(Creates) -> maps:fold(fun(CreationId, MsgData, {CreatedAcc, NotCreatedAcc}) -> Id = jchat_utils:generate_id(), case jchat_db:create_message(Id, MsgData) of {ok, Msg} -> JMAPMsg = message_to_jmap(Msg, null), {CreatedAcc#{CreationId => JMAPMsg}, NotCreatedAcc}; {error, Error} -> {CreatedAcc, NotCreatedAcc#{CreationId => jchat_utils:format_error(Error)}} end end, {#{}, #{}}, Creates). handle_message_updates(Updates) -> maps:fold(fun(Id, UpdateData, {UpdatedAcc, NotUpdatedAcc}) -> case jchat_db:update_message(Id, UpdateData) of {ok, Msg} -> JMAPMsg = message_to_jmap(Msg, null), {[JMAPMsg | UpdatedAcc], NotUpdatedAcc}; {error, Error} -> {UpdatedAcc, NotUpdatedAcc#{Id => jchat_utils:format_error(Error)}} end end, {[], #{}}, Updates). handle_message_destroys(Destroy) -> % Simplified - would implement actual deletion {Destroy, #{}}. conversation_to_jmap(#conversation{} = Conv, Properties) -> Base = #{ <<"id">> => Conv#conversation.id, <<"title">> => Conv#conversation.title, <<"description">> => Conv#conversation.description, <<"createdAt">> => Conv#conversation.created_at, <<"updatedAt">> => Conv#conversation.updated_at, <<"isArchived">> => Conv#conversation.is_archived, <<"isMuted">> => Conv#conversation.is_muted, <<"participantIds">> => Conv#conversation.participant_ids, <<"lastMessageId">> => Conv#conversation.last_message_id, <<"lastMessageAt">> => Conv#conversation.last_message_at, <<"unreadCount">> => Conv#conversation.unread_count, <<"messageCount">> => Conv#conversation.message_count, <<"metadata">> => Conv#conversation.metadata }, filter_properties(Base, Properties). message_to_jmap(#message{} = Msg, Properties) -> Base = #{ <<"id">> => Msg#message.id, <<"conversationId">> => Msg#message.conversation_id, <<"senderId">> => Msg#message.sender_id, <<"sentAt">> => Msg#message.sent_at, <<"receivedAt">> => Msg#message.received_at, <<"editedAt">> => Msg#message.edited_at, <<"body">> => Msg#message.body, <<"bodyType">> => Msg#message.body_type, <<"attachments">> => Msg#message.attachments, <<"replyToMessageId">> => Msg#message.reply_to_message_id, <<"isSystemMessage">> => Msg#message.is_system_message, <<"isDeleted">> => Msg#message.is_deleted, <<"reactions">> => Msg#message.reactions, <<"deliveryStatus">> => Msg#message.delivery_status, <<"readBy">> => Msg#message.read_by, <<"metadata">> => Msg#message.metadata }, filter_properties(Base, Properties). filter_properties(Map, null) -> Map; filter_properties(Map, Properties) -> maps:with(Properties, Map). get_state(Type) -> Key = {<<"default">>, Type}, % {account_id, object_type} case mnesia:dirty_read(state_counter, Key) of [#state_counter{state = State}] -> State; [] -> <<"0">> end.