import { useMutation, useQueryClient } from "@tanstack/react-query";
import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api";
import AgentProfilesService, {
  type AgentProfileListResponse,
} from "#/api/agent-profiles-service/agent-profiles-service.api";
import SettingsService from "#/api/settings-service/settings-service.api";
import { mergeAgentProfileSaveInput } from "#/components/features/settings/agent-profiles/merge-agent-profile-save-input";
import { useActiveBackend } from "#/contexts/active-backend-context";
import {
  AGENT_PROFILES_QUERY_KEYS,
  AGENT_PROFILES_RETRY_OPTIONS,
  SETTINGS_QUERY_KEYS,
} from "#/hooks/query/query-keys";
import { agentProfileDetailQueryKey } from "#/hooks/query/use-active-acp-profile-detail";
import { invalidateConversationQueries } from "./conversation-mutation-utils";

interface SwitchAcpModelVars {
  /**
   * When set, the ACP conversation's running model is swapped live via the
   * wrapper's ``session/set_model`` (POST /switch_acp_model) and the user's
   * saved default is untouched. When null (home page / no session), the model
   * is persisted as the default the next conversation launches with.
   */
  conversationId: string | null;
  model: string;
}

/**
 * ACP analog of {@link useSwitchLlmProfile}. Switches the ACP model
 * per-conversation when called from inside a conversation (live in-place model
 * switch; a created-but-not-yet-run conversation is a persist-only deferral on
 * the server, so blank conversations work too); persists it as the launch
 * default when called from the home page.
 *
 * The home-page persist target is the **active ACP AgentProfile**: profile
 * launches resolve the agent server-side purely from the stored profile
 * (``agent_profile_id`` and ``agent_settings`` are mutually exclusive launch
 * sources), so writing ``agent_settings.acp_model`` would not affect them.
 * Only when the profile list resolves with no active ACP profile does the
 * write fall back to the ``agent_settings_diff`` the legacy launch path
 * reads. A failed profile discovery propagates instead of downgrading
 * (#16523): an active-profile launch ignores agent_settings, so persisting
 * the pick there would silently drop it.
 *
 * Invalidates the same conversation/settings query keys the profile hook does
 * so the chat-input model chip + conversation chip refresh with the new model.
 */
export const useSwitchAcpModel = () => {
  const queryClient = useQueryClient();
  const { backend, orgId } = useActiveBackend();

  return useMutation({
    mutationFn: async ({ conversationId, model }: SwitchAcpModelVars) => {
      if (conversationId) {
        await AgentServerConversationService.switchAcpModel(
          conversationId,
          model,
        );
        return;
      }

      // Home page: resolve the active profile from the shared query cache
      // (same keys/retry policy as useCreateConversation's launch path, so a
      // pick can't disagree with what a subsequent send would launch). Like
      // that launch path, do not fall back to the legacy agent_settings
      // persist when profile discovery fails (#16523): an active-profile
      // launch ignores agent_settings, so the pick would be silently dropped.
      // The rejection surfaces via the global mutation error toast.
      const agentProfiles: AgentProfileListResponse =
        await queryClient.ensureQueryData({
          queryKey: [...AGENT_PROFILES_QUERY_KEYS.all, backend.id, orgId],
          queryFn: AgentProfilesService.listProfiles,
          ...AGENT_PROFILES_RETRY_OPTIONS,
        });
      const activeProfile = agentProfiles.profiles?.find(
        (profile) =>
          profile.id != null &&
          profile.id === agentProfiles.active_agent_profile_id,
      );

      if (activeProfile?.agent_kind === "acp") {
        // Merge over the stored profile so a model-only pick can't wipe the
        // fields this surface doesn't model (command, session mode, …).
        const detail = await queryClient.ensureQueryData({
          queryKey: agentProfileDetailQueryKey(
            backend.id,
            orgId,
            activeProfile.name,
          ),
          queryFn: () => AgentProfilesService.getProfile(activeProfile.name),
          ...AGENT_PROFILES_RETRY_OPTIONS,
        });
        await AgentProfilesService.saveProfile(
          activeProfile.name,
          mergeAgentProfileSaveInput(detail.profile, {
            agent_kind: "acp",
            acp_model: model,
          }),
        );
        return;
      }

      // No active ACP profile: persist as the agent-settings default the
      // legacy launch path reads. The backend deep-merges
      // ``agent_settings_diff`` into the existing ``agent_settings`` dict, so
      // a scalar ``acp_model`` diff updates only the model and preserves the
      // selected provider + command.
      await SettingsService.saveSettings({
        agent_settings_diff: { acp_model: model },
      });
    },
    onSuccess: (_data, { conversationId }) => {
      if (conversationId) {
        invalidateConversationQueries(queryClient, conversationId);
      } else {
        // Mirror useSwitchLlmProfile's home-page path: clear the stale settings
        // cache so the next conversation-start reads the newly saved default,
        // and refetch the settings query so the home-page chip updates. The
        // agent-profiles prefix covers both the list and the detail entry the
        // profile-persist path just rewrote.
        SettingsService.invalidateCache();
        queryClient.invalidateQueries({
          queryKey: SETTINGS_QUERY_KEYS.personal(),
        });
        queryClient.invalidateQueries({
          queryKey: AGENT_PROFILES_QUERY_KEYS.all,
        });
      }
    },
    // No meta.disableToast: unlike useSwitchLlmProfile (which tailors a
    // "Switched to {name} failed" message via its own onError), there's no
    // per-action message worth tailoring here, so the global mutation error
    // toast reports a failed switch / profile or settings write rather than
    // swallowing it.
  });
};
