// #region UseGitManager [C:3] [TYPE Module] [SEMANTICS git, handlers, composable, orchestration]
// @DEPRECATED [2026-05] Use GitManagerModel instead.
// All state and actions have been migrated to frontend/src/lib/models/GitManagerModel.svelte.js
// This file is kept for backward compatibility with existing components.
// New code should import GitManagerModel directly.
// @BRIEF Composable for GitManager handler functions — extracted to reduce component size per INV_7.
// @RELATION DEPENDS_ON -> [EXT:frontend:gitService]
// @RELATION DEPENDS_ON -> [GitUtils]
// @RATIONALE Extracted handler logic from GitManager (1220→~350 lines) to meet INV_7 <150 line contract limit.
import { gitService } from '../../services/gitService';
import { api } from '../../lib/api';
import { isNumericDashboardRef, resolveDefaultConfig, extractUnfinishedMergeContext, buildSuggestedRepoName } from '../../services/git-utils';

interface GitError {
  message: string;
  status: number;
  error_code: string | null;
  files: unknown[];
  next_steps: string[];
  detail: Record<string, unknown> | null;
  errorType?: string;
}

interface UnfinishedMergeContext {
  message: string;
  repositoryPath: string;
  gitDir: string;
  currentBranch: string;
  mergeHead: string;
  mergeMessagePreview: string;
  nextSteps: string[];
  commands: string[];
  conflictsCount?: number;
}

interface GitManagerState {
  dashboardId: string;
  envId: string | null;
  initialized: boolean;
  checkingStatus: boolean;
  loading: boolean;
  workspaceLoading: boolean;
  committing: boolean;
  generatingMessage: boolean;
  commitMessage: string;
  currentBranch: string;
  currentEnvStage: string;
  promoteFromBranch: string;
  promoteToBranch: string;
  preferredDeployTargetStage: string;
  promoteMode: string;
  promoteReason: string;
  promoting: boolean;
  isPulling: boolean;
  isPushing: boolean;
  autoPushAfterCommit: boolean;
  remoteUrl: string;
  selectedConfigId: string | null;
  configs: Array<{ id: string; provider?: string; is_default?: boolean; status?: string; [key: string]: unknown }>;
  repositoryProvider: string;
  dashboardTitle: string;
  gitError: GitError | null;
  gitErrorType: string;
  unfinishedMergeContext: UnfinishedMergeContext | null;
  showUnfinishedMergeDialog: boolean;
  mergeRecoveryLoading: boolean;
  mergeConflicts: unknown[];
  showConflictResolver: boolean;
  mergeResolveInProgress: boolean;
  mergeAbortInProgress: boolean;
  mergeContinueInProgress: boolean;
  copyingUnfinishedMergeCommands: boolean;
  creatingRemoteRepo: boolean;
  hasWorkspaceChanges: boolean;
  workspaceStatus: unknown;
  workspaceDiff: string;
  showDeployModal: boolean;
  resolveEnvId: () => string | null;
  normalizeEnvStageFn: (env: Record<string, unknown>) => string;
  applyGitflowStageDefaultsFn: (stage: string) => { promoteFromBranch: string; promoteToBranch: string; preferredDeployTargetStage: string };
  toast: (message: string, type?: string, duration?: number) => void;
  t?: {
    git?: Record<string, string | Record<string, string>>;
    [key: string]: unknown;
  };
  [key: string]: unknown;
}

type SetStateFn = (patch: Partial<GitManagerState>) => void;

// #region buildGitErrorFromException [C:2] [TYPE Function]
// @BRIEF Extract structured gitError payload from a caught exception for modal error banner.
// @PRE  e is an Error object with optional .status, .detail, .error_code properties.
// @POST Returns { message, status, error_code, files, next_steps, detail } for state.
function buildGitErrorFromException(e: Record<string, unknown> | null | undefined): GitError {
  if (!e) return { message: 'Неизвестная ошибка Git', status: 500, error_code: null, files: [], next_steps: [], detail: null };
  const detail = e?.detail || (typeof e?.detail === 'string' ? { message: e.detail } : null) || null;
  const detailObj = typeof detail === 'object' ? detail as Record<string, unknown> : null;
  return {
    message: (detailObj?.message as string) || (detailObj?.message_en as string) || (e.message as string) || 'Произошла ошибка Git',
    status: (e?.status as number) || 500,
    error_code: (e?.error_code as string) || (detailObj?.error_code as string) || null,
    files: (detailObj?.files as unknown[]) || [],
    next_steps: (detailObj?.next_steps as string[]) || (detailObj?.next_steps_en as string[]) || [],
    detail: detailObj,
  };
}
// #endregion buildGitErrorFromException

interface GitHandlers {
  loadCurrentEnvironmentStage: () => Promise<void>;
  checkStatus: () => Promise<void>;
  loadWorkspace: () => Promise<void>;
  handleSync: () => Promise<void>;
  handleGenerateMessage: () => Promise<void>;
  handleCommit: () => Promise<void>;
  openUnfinishedMergeDialogFromError: (error: Record<string, unknown>) => boolean;
  loadMergeRecoveryState: () => Promise<void>;
  closeUnfinishedMergeDialog: () => void;
  handleOpenConflictResolver: () => Promise<void>;
  handleResolveConflicts: (event: { detail?: Record<string, string> }) => Promise<void>;
  handleAbortUnfinishedMerge: () => Promise<void>;
  handleContinueUnfinishedMerge: () => Promise<void>;
  getUnfinishedMergeCommandsText: () => string;
  handleCopyUnfinishedMergeCommands: () => Promise<void>;
  handlePull: () => Promise<void>;
  handlePush: () => Promise<void>;
  handlePromote: () => Promise<void>;
  openDeployModal: () => void;
  handleCreateRemoteRepo: () => Promise<void>;
  handleInit: () => Promise<void>;
}

export function createGitHandlers(getState: () => GitManagerState, setState: SetStateFn): GitHandlers {
  const s = () => getState();
  const u = (patch: Partial<GitManagerState>) => setState(patch);

  // ── Helper: clear modal error banner ──
  function clearError() {
    u({ gitError: null, gitErrorType: 'error' });
  }

  // ── Helper: set modal error banner + persistent toast ──
  function setGitError(e: Record<string, unknown>, errorType?: string) {
    const err = buildGitErrorFromException(e);
    err.errorType = errorType || (err.status >= 500 ? 'error' : err.status === 409 ? 'warning' : 'error');
    u({ gitError: err, gitErrorType: err.errorType });
    // Show persistent toast above the modal (duration=0 = no auto-dismiss)
    if (s().toast) {
      const type = err.errorType === 'warning' ? 'warning' : 'error';
      s().toast(err.message, type, 0);
    }
  }

  // ── Use const reference instead of `this` to avoid context loss in callbacks ──
  const handlers: GitHandlers = {

    // #region loadCurrentEnvironmentStage [C:2] [TYPE Function]
    // @BRIEF Resolve current environment stage (DEV/PREPROD/PROD) and apply GitFlow defaults.
    // @PRE  resolveEnvId() returns a valid environment ID, or handler exits silently.
    // @POST currentEnvStage and GitFlow defaults (promoteFromBranch, promoteToBranch) are set.
    // @SIDE_EFFECT Fetches /environments from API.
    async loadCurrentEnvironmentStage(): Promise<void> {
      const currentEnvId = s().resolveEnvId();
      if (!currentEnvId) return;
      try {
        const environments = await api.getEnvironmentsList<{ id: string; is_production?: boolean; stage?: string; [key: string]: unknown }[]>();
        const currentEnv = (environments || []).find((item: { id: string }) => item.id === currentEnvId);
        if (!currentEnv) return;
        const stage = s().normalizeEnvStageFn(currentEnv as Record<string, unknown>);
        u({ currentEnvStage: stage });
        const defaults = s().applyGitflowStageDefaultsFn(stage);
        u(defaults);
      } catch (e: unknown) {
        console.error(`[GitManager][Coherence:Failed] Failed to resolve environment stage: ${(e as Error).message}`);
      }
    },
    // #endregion loadCurrentEnvironmentStage

    // #region checkStatus [C:3] [TYPE Function]
    // @BRIEF Check if Git repository is initialized for the dashboard.
    // @PRE  dashboardId is a non-numeric slug; envId is required (resolved with localStorage fallback).
    // @POST initialized=true if repository found and workspace loaded; initialized=false if not found or envId missing.
    // @SIDE_EFFECT Fetches /git/repositories/{ref}/branches; may fetch /git/repositories/{ref}/status and /diff on success.
    async checkStatus(): Promise<void> {
      if (isNumericDashboardRef(s().dashboardId)) {
        u({ checkingStatus: false, initialized: false });
        s().toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
        return;
      }
      u({ checkingStatus: true });
      if (!s().envId) {
        u({ initialized: false, checkingStatus: false });
        return;
      }
      try {
        await gitService.getBranches(s().dashboardId, s().envId);
        u({ initialized: true });
        await handlers.loadWorkspace();
      } catch (_e: unknown) {
        u({ initialized: false });
      } finally { u({ checkingStatus: false }); }
    },
    // #endregion checkStatus

    // #region loadWorkspace [C:2] [TYPE Function]
    // @BRIEF Load Git workspace status and diff for initialized repository.
    // @PRE  initialized is true; envId is required when ref is slug.
    // @POST workspaceStatus and workspaceDiff are updated; currentBranch synced from server.
    // @SIDE_EFFECT Fetches /status and /diff for the dashboard repository.
    async loadWorkspace(): Promise<void> {
      if (!s().initialized) return;
      clearError();
      u({ workspaceLoading: true });
      try {
        const ws = await gitService.getStatus(s().dashboardId, s().envId);
        const sd = await gitService.getDiff<string>(s().dashboardId, null, true, s().envId);
        const ud = await gitService.getDiff<string>(s().dashboardId, null, false, s().envId);
        u({ workspaceStatus: ws, workspaceDiff: [sd, ud].filter(Boolean).join('\n\n'), currentBranch: (ws as Record<string, unknown>)?.current_branch as string || s().currentBranch });
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ workspaceLoading: false }); }
    },
    // #endregion loadWorkspace

    // #region handleSync [C:2] [TYPE Function]
    // @BRIEF Synchronize local dashboard state with Git repository.
    // @PRE  dashboardId is a non-numeric slug; envId required when ref is slug.
    // @POST Dashboard state synced to Git workspace; workspace reloaded.
    // @SIDE_EFFECT POSTs to /git/repositories/{ref}/sync; reloads workspace on success.
    async handleSync(): Promise<void> {
      if (isNumericDashboardRef(s().dashboardId)) { s().toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error'); return; }
      clearError();
      u({ loading: true });
      try {
        const sourceEnvId = s().envId || localStorage.getItem('selected_env_id');
        await gitService.sync(s().dashboardId, sourceEnvId, s().envId);
        s().toast(s().t?.git?.sync_success as string || 'Состояние дашборда синхронизировано с Git', 'success');
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ loading: false }); }
    },
    // #endregion handleSync

    // #region handleGenerateMessage [C:2] [TYPE Function]
    // @BRIEF Generate AI commit message from dashboard diff.
    // @SIDE_EFFECT POSTs to /git/repositories/{ref}/generate-message.
    // @POST commitMessage state updated with generated text.
    async handleGenerateMessage(): Promise<void> {
      clearError();
      u({ generatingMessage: true });
      try {
        const data = await api.postApi<{ message?: string }>(`/git/repositories/${encodeURIComponent(String(s().dashboardId))}/generate-message${s().envId ? `?env_id=${encodeURIComponent(String(s().envId))}` : ''}`, undefined, { suppressToast: true });
        u({ commitMessage: data?.message || '' });
        s().toast(s().t?.git?.commit_message_generated as string || 'Сообщение для коммита сгенерировано', 'success');
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ generatingMessage: false }); }
    },
    // #endregion handleGenerateMessage

    // #region handleCommit [C:2] [TYPE Function]
    // @BRIEF Stage and commit workspace changes, optionally push.
    // @PRE  commitMessage is non-empty; hasWorkspaceChanges is true; envId required when ref is slug.
    // @POST Changes committed (and pushed if autoPushAfterCommit); workspace reloaded.
    // @SIDE_EFFECT POSTs to /commit and optionally /push.
    async handleCommit(): Promise<void> {
      if (!s().commitMessage || !s().hasWorkspaceChanges) return;
      clearError();
      u({ committing: true });
      try {
        await gitService.commit(s().dashboardId, s().commitMessage, [], s().envId);
        if (s().autoPushAfterCommit) {
          await gitService.push(s().dashboardId, s().envId);
          s().toast(s().t?.git?.commit_and_push_success as string || 'Коммит создан и отправлен в remote', 'success');
        } else { s().toast(s().t?.git?.commit_success as string || 'Коммит успешно создан', 'success'); }
        u({ commitMessage: '' });
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ committing: false }); }
    },
    // #endregion handleCommit

    // #region openUnfinishedMergeDialogFromError [C:2] [TYPE Function]
    // @BRIEF Parse error for unfinished merge context and open dialog.
    // @PRE  error contains GIT_UNFINISHED_MERGE error_code or related detail.
    // @POST Returns true and opens dialog if merge context found; false otherwise.
    openUnfinishedMergeDialogFromError(error: Record<string, unknown>): boolean {
      const context = extractUnfinishedMergeContext(error as Record<string, unknown> | null | undefined);
      if (!context) return false;
      u({ unfinishedMergeContext: context, showUnfinishedMergeDialog: true });
      return true;
    },
    // #endregion openUnfinishedMergeDialogFromError

    // #region loadMergeRecoveryState [C:2] [TYPE Function]
    // @BRIEF Load merge recovery state from backend for unfinished merge.
    // @PRE  Repository exists; envId required when ref is slug.
    // @POST unfinishedMergeContext is populated; dialog closed if no merge in progress.
    // @SIDE_EFFECT GETs /merge/status from backend.
    async loadMergeRecoveryState(): Promise<void> {
      clearError();
      u({ mergeRecoveryLoading: true });
      try {
        const status = await gitService.getMergeStatus<{ has_unfinished_merge?: boolean; repository_path?: string; git_dir?: string; current_branch?: string; merge_head?: string; merge_message_preview?: string; conflicts_count?: number }>(s().dashboardId, s().envId);
        if (!status?.has_unfinished_merge) { handlers.closeUnfinishedMergeDialog(); return; }
        u({
          unfinishedMergeContext: {
            ...(s().unfinishedMergeContext || {}),
            message: s().unfinishedMergeContext?.message || ((s().t?.git as Record<string, unknown>)?.unfinished_merge as Record<string, string>)?.default_message || '',
            repositoryPath: String(status.repository_path || s().unfinishedMergeContext?.repositoryPath || ''),
            gitDir: String(status.git_dir || s().unfinishedMergeContext?.gitDir || ''),
            currentBranch: String(status.current_branch || s().unfinishedMergeContext?.currentBranch || ''),
            mergeHead: String(status.merge_head || s().unfinishedMergeContext?.mergeHead || ''),
            mergeMessagePreview: String(status.merge_message_preview || ''),
            nextSteps: Array.isArray(s().unfinishedMergeContext?.nextSteps) ? s().unfinishedMergeContext.nextSteps : [],
            commands: Array.isArray(s().unfinishedMergeContext?.commands) ? s().unfinishedMergeContext.commands : [],
            conflictsCount: Number(status.conflicts_count || 0),
          } as UnfinishedMergeContext
        });
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ mergeRecoveryLoading: false }); }
    },
    // #endregion loadMergeRecoveryState

    // #region closeUnfinishedMergeDialog [C:1] [TYPE Function]
    // @BRIEF Close unfinished merge dialog and reset merge state.
    // @POST Dialog hidden; mergeConflicts, showConflictResolver reset.
    closeUnfinishedMergeDialog(): void {
      u({ showUnfinishedMergeDialog: false, unfinishedMergeContext: null, mergeConflicts: [], showConflictResolver: false });
    },
    // #endregion closeUnfinishedMergeDialog

    // #region handleOpenConflictResolver [C:2] [TYPE Function]
    // @BRIEF Load merge conflicts and open conflict resolver UI.
    // @PRE  Unfinished merge is in progress; envId required when ref is slug.
    // @POST mergeConflicts populated; showConflictResolver set to true if conflicts found.
    // @SIDE_EFFECT GETs /merge/conflicts from backend.
    async handleOpenConflictResolver(): Promise<void> {
      clearError();
      u({ mergeRecoveryLoading: true });
      try {
        const mc = await gitService.getMergeConflicts(s().dashboardId, s().envId);
        if (!Array.isArray(mc) || mc.length === 0) { s().toast(s().t?.git?.unfinished_merge?.no_conflicts as string || 'No unresolved conflicts were found', 'info'); return; }
        u({ mergeConflicts: mc, showConflictResolver: true });
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ mergeRecoveryLoading: false }); }
    },
    // #endregion handleOpenConflictResolver

    // #region handleResolveConflicts [C:2] [TYPE Function]
    // @BRIEF Apply conflict resolutions and stage resolved files.
    // @PRE  event.detail maps file paths to resolution strategies; envId required when ref is slug.
    // @POST Conflicts resolved and staged; resolver closed; workspace reloaded.
    async handleResolveConflicts(event: { detail?: Record<string, string> }): Promise<void> {
      const detail = event?.detail || {};
      const resolutions = Object.entries(detail).map(([fp, r]) => ({ file_path: fp, resolution: r }));
      if (!resolutions.length) { s().toast(s().t?.git?.unfinished_merge?.resolve_empty as string || 'No conflict resolutions selected', 'warning'); return; }
      clearError();
      u({ mergeResolveInProgress: true });
      try {
        await gitService.resolveMergeConflicts(s().dashboardId, resolutions, s().envId);
        s().toast(s().t?.git?.unfinished_merge?.resolve_success as string || 'Conflicts were resolved and staged', 'success');
        u({ showConflictResolver: false });
        await handlers.loadMergeRecoveryState();
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ mergeResolveInProgress: false }); }
    },
    // #endregion handleResolveConflicts

    // #region handleAbortUnfinishedMerge [C:2] [TYPE Function]
    // @BRIEF Abort current unfinished merge.
    // @PRE  envId required when ref is slug.
    // @POST Merge aborted; dialog closed; workspace reloaded.
    async handleAbortUnfinishedMerge(): Promise<void> {
      clearError();
      u({ mergeAbortInProgress: true });
      try {
        await gitService.abortMerge(s().dashboardId, s().envId);
        s().toast(s().t?.git?.unfinished_merge?.abort_success as string || 'Merge was aborted', 'success');
        handlers.closeUnfinishedMergeDialog();
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ mergeAbortInProgress: false }); }
    },
    // #endregion handleAbortUnfinishedMerge

    // #region handleContinueUnfinishedMerge [C:2] [TYPE Function]
    // @BRIEF Finalize unfinished merge by creating merge commit.
    // @PRE  All conflicts resolved; envId required when ref is slug.
    // @POST Merge commit created; dialog closed; workspace reloaded.
    async handleContinueUnfinishedMerge(): Promise<void> {
      clearError();
      u({ mergeContinueInProgress: true });
      try {
        await gitService.continueMerge(s().dashboardId, '', s().envId);
        s().toast(s().t?.git?.unfinished_merge?.continue_success as string || 'Merge commit created successfully', 'success');
        handlers.closeUnfinishedMergeDialog();
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
        await handlers.loadMergeRecoveryState();
      } finally { u({ mergeContinueInProgress: false }); }
    },
    // #endregion handleContinueUnfinishedMerge

    // #region getUnfinishedMergeCommandsText [C:1] [TYPE Function]
    // @BRIEF Get shell commands for manual merge recovery.
    // @POST Returns concatenated command string or empty string.
    getUnfinishedMergeCommandsText(): string {
      if (!s().unfinishedMergeContext?.commands?.length) return '';
      return s().unfinishedMergeContext.commands.join('\n');
    },
    // #endregion getUnfinishedMergeCommandsText

    // #region handleCopyUnfinishedMergeCommands [C:1] [TYPE Function]
    // @BRIEF Copy merge recovery commands to clipboard.
    // @PRE  commands exist in unfinishedMergeContext.
    // @POST Commands copied to clipboard; toast shown.
    async handleCopyUnfinishedMergeCommands(): Promise<void> {
      const text = handlers.getUnfinishedMergeCommandsText();
      if (!text) { s().toast(s().t?.git?.unfinished_merge?.copy_empty as string || 'Команды для копирования отсутствуют', 'warning'); return; }
      u({ copyingUnfinishedMergeCommands: true });
      try {
        await navigator.clipboard.writeText(text);
        s().toast(s().t?.git?.unfinished_merge?.copy_success as string || 'Команды скопированы в буфер обмена', 'success');
      } catch (_e: unknown) { s().toast(s().t?.git?.unfinished_merge?.copy_failed as string || 'Не удалось скопировать команды', 'error'); } finally { u({ copyingUnfinishedMergeCommands: false }); }
    },
    // #endregion handleCopyUnfinishedMergeCommands

    // #region handlePull [C:2] [TYPE Function]
    // @BRIEF Pull changes from remote repository.
    // @PRE  Remote 'origin' configured; envId required when ref is slug.
    // @POST Local repository updated with remote changes; workspace reloaded.
    // @SIDE_EFFECT POSTs to /pull; may open merge dialog on GIT_UNFINISHED_MERGE.
    async handlePull(): Promise<void> {
      clearError();
      u({ isPulling: true });
      try {
        await gitService.pull(s().dashboardId, s().envId);
        s().toast(s().t?.git?.pull_success as string || 'Изменения получены из Git', 'success');
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        const handled = handlers.openUnfinishedMergeDialogFromError(e as Record<string, unknown>);
        if (handled) {
          await handlers.loadMergeRecoveryState();
        } else {
          setGitError(e as Record<string, unknown>);
        }
      } finally { u({ isPulling: false }); }
    },
    // #endregion handlePull

    // #region handlePush [C:2] [TYPE Function]
    // @BRIEF Push local commits to remote repository.
    // @PRE  Remote 'origin' configured; envId required when ref is slug.
    // @POST Remote updated with local commits; workspace reloaded.
    async handlePush(): Promise<void> {
      clearError();
      u({ isPushing: true });
      try {
        await gitService.push(s().dashboardId, s().envId);
        s().toast(s().t?.git?.push_success as string || 'Изменения отправлены в Git', 'success');
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ isPushing: false }); }
    },
    // #endregion handlePush

    // #region handlePromote [C:3] [TYPE Function]
    // @BRIEF Promote changes between branches via MR or direct merge.
    // @PRE  promoteFromBranch and promoteToBranch differ; direct mode requires reason; envId required when ref is slug.
    // @POST MR created on Git server (MR mode) or direct merge executed (direct mode).
    // @SIDE_EFFECT Opens MR URL in new tab (MR mode); writes policy violation to logs (direct mode).
    async handlePromote(): Promise<void> {
      const st = s();
      if (!st.promoteFromBranch || !st.promoteToBranch || st.promoteFromBranch === st.promoteToBranch) { st.toast('Выберите разные исходную и целевую ветки', 'error'); return; }
      if (st.promoteMode === 'direct' && !String(st.promoteReason || '').trim()) { st.toast('Для небезопасного прямого переноса укажите причину', 'error'); return; }
      clearError();
      u({ promoting: true });
      try {
        const response = await gitService.promote<{ url?: string }>(st.dashboardId, {
          from_branch: st.promoteFromBranch, to_branch: st.promoteToBranch, mode: st.promoteMode,
          title: `Promote ${st.promoteFromBranch} -> ${st.promoteToBranch}: ${st.dashboardTitle || st.dashboardId}`,
          description: st.promoteMode === 'direct' ? `Unsafe direct promote.\nReason: ${st.promoteReason}` : undefined,
          reason: st.promoteMode === 'direct' ? st.promoteReason : undefined,
        }, st.envId);
        if (st.promoteMode === 'direct') { st.toast('Прямой перенос выполнен. Нарушение политики записано в логи.', 'warning'); }
        else { if (response?.url) window.open(response.url, '_blank', 'noopener,noreferrer'); st.toast('Merge Request создан на Git сервере', 'success'); }
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ promoting: false }); }
    },
    // #endregion handlePromote

    // #region openDeployModal [C:2] [TYPE Function]
    // @BRIEF Open deployment target modal with PROD confirmation.
    // @PRE  dashboardId is a valid slug.
    // @POST showDeployModal set to true if PROD confirmation passes.
    openDeployModal(): void {
      const st = s();
      if (st.currentEnvStage === 'PROD') {
        const expected = String(st.dashboardId);
        const confirmation = prompt(`Подтвердите деплой в PROD. Введите slug дашборда: ${expected}`);
        if (String(confirmation || '').trim() !== expected) { st.toast('Подтверждение PROD не пройдено. Деплой отменен.', 'error'); return; }
      }
      u({ showDeployModal: true });
    },
    // #endregion openDeployModal

    // #region handleCreateRemoteRepo [C:2] [TYPE Function]
    // @BRIEF Create a remote repository on the selected Git provider.
    // @PRE  A Git config is selected; user provides a repo name via prompt.
    // @POST remoteUrl set to created (or existing) repository clone URL.
    // @SIDE_EFFECT POSTs to /git/config/{id}/repositories; prompts user for repo name.
    async handleCreateRemoteRepo(): Promise<void> {
      const config = resolveDefaultConfig(s().configs, s().selectedConfigId);
      if (!config) { s().toast(s().t?.git?.init_validation_error as string || 'Сначала выберите Git сервер', 'error'); return; }
      if (!s().selectedConfigId && config.id) u({ selectedConfigId: config.id });
      const suggestedName = buildSuggestedRepoName(s().dashboardTitle, s().dashboardId);
      const inputName = prompt(`Repository name for ${config.provider}:`, suggestedName);
      const repoName = String(inputName || '').trim();
      if (!repoName) return;
      clearError();
      u({ creatingRemoteRepo: true });
      try {
        const repo = await gitService.createRemoteRepository<{ clone_url?: string; html_url?: string }>(config.id, {
          name: repoName, private: true,
          description: `Superset dashboard ${s().dashboardId}: ${s().dashboardTitle || repoName}`,
          auto_init: true, default_branch: 'main',
        });
        const url = repo?.clone_url || repo?.html_url || '';
        if (!url) throw new Error('Remote repository created, but URL is empty');
        u({ remoteUrl: url });
        s().toast(`Repository created on ${config.provider}`, 'success');
      } catch (e: unknown) {
        const err = e as Record<string, unknown>;
        if (err?.status === 409 && /already exists/i.test(String(err?.message || ''))) {
          s().toast(s().t?.git?.repo_already_exists as string || 'Repository already exists. Enter its URL below and click Init.', 'warning', 0);
        } else {
          setGitError(err, 'warning');
        }
      } finally { u({ creatingRemoteRepo: false }); }
    },
    // #endregion handleCreateRemoteRepo

    // #region handleInit [C:3] [TYPE Function]
    // @BRIEF Initialize local Git repository for the dashboard: clone remote.
    // @PRE  selectedConfigId is set; remoteUrl is set; envId required when ref is slug.
    // @POST Repository initialized; initialized=true; workspace loaded.
    // @SIDE_EFFECT POSTs to /git/repositories/{ref}/init; loads workspace on success.
    async handleInit(): Promise<void> {
      if (!s().selectedConfigId || !s().remoteUrl) { s().toast(s().t?.git?.init_validation_error as string || 'Заполните все поля', 'error'); return; }
      // ── Guard: slug-based init requires envId ──
      if (!s().envId && !isNumericDashboardRef(s().dashboardId)) {
        s().toast('Environment must be selected to initialize Git for this dashboard.', 'error');
        return;
      }
      clearError();
      u({ loading: true });
      try {
        await gitService.initRepository(s().dashboardId, s().selectedConfigId, s().remoteUrl, s().envId);
        s().toast(s().t?.git?.init_success as string || 'Репозиторий инициализирован', 'success');
        const provider = resolveDefaultConfig(s().configs, s().selectedConfigId)?.provider || '';
        u({ initialized: true, repositoryProvider: provider });
        await handlers.loadWorkspace();
      } catch (e: unknown) {
        setGitError(e as Record<string, unknown>);
      } finally { u({ loading: false }); }
    },
    // #endregion handleInit
  };
  return handlers;
}
// #endregion UseGitManager
